Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
40bbd2a
feat: add log get-level command
SgtPooki Jul 24, 2025
44e1119
chore: update changelog
SgtPooki Jul 24, 2025
9839fa4
test: add log get-level tests
SgtPooki Jul 24, 2025
254f5c9
chore: cleanup
SgtPooki Jul 24, 2025
970e687
chore: fix typo and harden tests
lidel Jul 24, 2025
c5b7d21
fix: TestCommands
lidel Jul 24, 2025
32c8363
docs: relation to GOLOG_LOG_LEVEL
lidel Jul 25, 2025
5b2597f
chore(go.mod): switch to PR dependency
lidel Jul 25, 2025
8adcf34
chore: update to latest go-log PR changes
SgtPooki Jul 28, 2025
1042bab
fix: GetLogLevel requires an argument
SgtPooki Jul 28, 2025
987d378
fix: do not output single subsystem name in CLI
SgtPooki Jul 28, 2025
b7489b9
test: explicit subsystem request dont output subsystem
SgtPooki Jul 28, 2025
0d910d0
update to new release og go-log
gammazero Jul 29, 2025
dc2cc3f
Use go-log v2.8.0. Use default keyword to identify default level
gammazero Jul 30, 2025
95835cb
LevelFromString renamed to Parse
gammazero Jul 30, 2025
6990fef
Recognize keywords "all", "default", and "*" when setting log levels
gammazero Jul 31, 2025
3b725d7
Merge branch 'master' into fix/add-api-v0-log--get-level
gammazero Aug 6, 2025
9a6d341
Modify `ipfs log level` to show log levels
gammazero Aug 6, 2025
2ee9c40
Denote default level with sdubsystem name '(defult)'. Fix tests
gammazero Aug 6, 2025
a9521a7
fix help formatting
gammazero Aug 6, 2025
0cd62f2
test: extra CLI/RPC tests for examples from --help
lidel Aug 6, 2025
2b5ab33
refactor: restore 'all' alias for '*'
lidel Aug 6, 2025
fb0f89d
Merge branch 'master' into fix/add-api-v0-log--get-level
gammazero Aug 11, 2025
fefffdb
Merge branch 'master' into fix/add-api-v0-log--get-level
lidel Aug 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/ipfs/kubo/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ func insideGUI() bool {
func checkDebug(req *cmds.Request) {
// check if user wants to debug. option OR env var.
debug, _ := req.Options["debug"].(bool)
ipfsLogLevel, _ := logging.LevelFromString(os.Getenv("IPFS_LOGGING")) // IPFS_LOGGING is deprecated
goLogLevel, _ := logging.LevelFromString(os.Getenv("GOLOG_LOG_LEVEL"))
ipfsLogLevel, _ := logging.Parse(os.Getenv("IPFS_LOGGING")) // IPFS_LOGGING is deprecated
goLogLevel, _ := logging.Parse(os.Getenv("GOLOG_LOG_LEVEL"))

if debug || goLogLevel == logging.LevelDebug || ipfsLogLevel == logging.LevelDebug {
u.Debug = true
Expand Down
194 changes: 164 additions & 30 deletions core/commands/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,37 @@ package commands
import (
"fmt"
"io"
"slices"

cmds "github.com/ipfs/go-ipfs-cmds"
logging "github.com/ipfs/go-log/v2"
)

// Golang os.Args overrides * and replaces the character argument with
// an array which includes every file in the user's CWD. As a
// workaround, we use 'all' instead. The util library still uses * so
// we convert it at this step.
var logAllKeyword = "all"
const (
// allLogSubsystems is used to specify all log subsystems when setting the
// log level.
allLogSubsystems = "*"
// allLogSubsystemsAlias is a convenience alias for allLogSubsystems that
// doesn't require shell escaping.
allLogSubsystemsAlias = "all"
// defaultLogLevel is used to request and to identify the default log
// level.
defaultLogLevel = "default"
// defaultSubsystemKey is the subsystem name that is used to denote the
// default log level. We use parentheses for UI clarity to distinguish it
// from regular subsystem names.
defaultSubsystemKey = "(default)"
// logLevelOption is an option for the tail subcommand to select the log
// level to output.
logLevelOption = "log-level"
// noSubsystemSpecified is used when no subsystem argument is provided
noSubsystemSpecified = ""
)

type logLevelOutput struct {
Levels map[string]string `json:",omitempty"`
Message string `json:",omitempty"`
}

var LogCmd = &cmds.Command{
Helptext: cmds.HelpText{
Expand All @@ -39,46 +60,161 @@ system (not just for the daemon logs, but all commands):

var logLevelCmd = &cmds.Command{
Helptext: cmds.HelpText{
Tagline: "Change the logging level.",
Tagline: "Change or get the logging level.",
ShortDescription: `
Change the verbosity of one or all subsystems log output. This does not affect
the event log.
Get or change the logging level of one or all logging subsystems.

This command provides a runtime alternative to the GOLOG_LOG_LEVEL
environment variable for debugging and troubleshooting.

UNDERSTANDING DEFAULT vs '*':

The "default" level is the fallback used by unconfigured subsystems.
You cannot set the default level directly - it only changes when you use '*'.

The '*' wildcard represents ALL subsystems including the default level.
Setting '*' changes everything at once, including the default.

EXAMPLES - Getting levels:

ipfs log level # Show only the default fallback level
ipfs log level all # Show all subsystem levels (100+ lines)
ipfs log level core # Show level for 'core' subsystem only

EXAMPLES - Setting levels:

ipfs log level core debug # Set 'core' to 'debug' (default unchanged)
ipfs log level all info # Set ALL to 'info' (including default)
ipfs log level core default # Reset 'core' to use current default level

WILDCARD OPTIONS:

Use 'all' (convenient) or '*' (requires escaping) to affect all subsystems:
ipfs log level all debug # Convenient - no shell escaping needed
ipfs log level '*' debug # Equivalent but needs quotes: '*' or "*" or \*

BEHAVIOR EXAMPLES:

Initial state (all using default 'error'):
$ ipfs log level => error
$ ipfs log level core => error

After setting one subsystem:
$ ipfs log level core debug
$ ipfs log level => error (default unchanged!)
$ ipfs log level core => debug (explicitly set)
$ ipfs log level dht => error (still uses default)

After setting everything with 'all':
$ ipfs log level all info
$ ipfs log level => info (default changed!)
$ ipfs log level core => info (all changed)
$ ipfs log level dht => info (all changed)

The 'default' keyword always refers to the current default level:
$ ipfs log level => error
$ ipfs log level core default # Sets core to 'error'
$ ipfs log level all info # Changes default to 'info'
$ ipfs log level core default # Now sets core to 'info'
`,
},

Arguments: []cmds.Argument{
// TODO use a different keyword for 'all' because all can theoretically
// clash with a subsystem name
cmds.StringArg("subsystem", true, false, fmt.Sprintf("The subsystem logging identifier. Use '%s' for all subsystems.", logAllKeyword)),
cmds.StringArg("level", true, false, `The log level, with 'debug' the most verbose and 'fatal' the least verbose.
One of: debug, info, warn, error, dpanic, panic, fatal.
`),
cmds.StringArg("subsystem", false, false, fmt.Sprintf("The subsystem logging identifier. Use '%s' or '%s' to get or set the log level of all subsystems including the default. If not specified, only show the default log level.", allLogSubsystemsAlias, allLogSubsystems)),
cmds.StringArg("level", false, false, fmt.Sprintf("The log level, with 'debug' as the most verbose and 'fatal' the least verbose. Use '%s' to set to the current default level. One of: debug, info, warn, error, dpanic, panic, fatal, %s", defaultLogLevel, defaultLogLevel)),
},
NoLocal: true,
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
args := req.Arguments
subsystem, level := args[0], args[1]
var level, subsystem string

if subsystem == logAllKeyword {
subsystem = "*"
if len(req.Arguments) > 0 {
subsystem = req.Arguments[0]
if len(req.Arguments) > 1 {
level = req.Arguments[1]
}

// Normalize aliases to the canonical "*" form
if subsystem == allLogSubsystems || subsystem == allLogSubsystemsAlias {
subsystem = "*"
}
}

if err := logging.SetLogLevel(subsystem, level); err != nil {
return err
// If a level is specified, then set the log level.
if level != "" {
if level == defaultLogLevel {
level = logging.DefaultLevel().String()
}

if err := logging.SetLogLevel(subsystem, level); err != nil {
return err
}

s := fmt.Sprintf("Changed log level of '%s' to '%s'\n", subsystem, level)
log.Info(s)

return cmds.EmitOnce(res, &logLevelOutput{Message: s})
}

s := fmt.Sprintf("Changed log level of '%s' to '%s'\n", subsystem, level)
log.Info(s)
// Get the level for the requested subsystem.
switch subsystem {
case noSubsystemSpecified:
// Return the default log level
levelMap := map[string]string{logging.DefaultName: logging.DefaultLevel().String()}
return cmds.EmitOnce(res, &logLevelOutput{Levels: levelMap})
case allLogSubsystems, allLogSubsystemsAlias:
// Return levels for all subsystems (default behavior)
levels := logging.SubsystemLevelNames()

// Replace default subsystem key with defaultSubsystemKey.
levels[defaultSubsystemKey] = levels[logging.DefaultName]
delete(levels, logging.DefaultName)
return cmds.EmitOnce(res, &logLevelOutput{Levels: levels})
default:
// Return level for a specific subsystem.
level, err := logging.SubsystemLevelName(subsystem)
if err != nil {
return err
}
levelMap := map[string]string{subsystem: level}
return cmds.EmitOnce(res, &logLevelOutput{Levels: levelMap})
}

return cmds.EmitOnce(res, &MessageOutput{s})
},
Encoders: cmds.EncoderMap{
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *MessageOutput) error {
fmt.Fprint(w, out.Message)
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *logLevelOutput) error {
if out.Message != "" {
fmt.Fprint(w, out.Message)
return nil
}

// Check if this is an RPC call by looking for the encoding option
encoding, _ := req.Options["encoding"].(string)
isRPC := encoding == "json"

// Determine whether to show subsystem names in output.
// Show subsystem names when:
// 1. It's an RPC call (needs JSON structure with named fields)
// 2. Multiple subsystems are displayed (for clarity when showing many levels)
showNames := isRPC || len(out.Levels) > 1

levelNames := make([]string, 0, len(out.Levels))
for subsystem, level := range out.Levels {
if showNames {
// Show subsystem name when it's RPC or when showing multiple subsystems
levelNames = append(levelNames, fmt.Sprintf("%s: %s", subsystem, level))
} else {
// For CLI calls with single subsystem, only show the level
levelNames = append(levelNames, level)
}
}
slices.Sort(levelNames)
for _, ln := range levelNames {
fmt.Fprintln(w, ln)
}
return nil
}),
},
Type: MessageOutput{},
Type: logLevelOutput{},
}

var logLsCmd = &cmds.Command{
Expand All @@ -103,12 +239,10 @@ subsystems of a running daemon.
Type: stringList{},
}

const logLevelOption = "log-level"

var logTailCmd = &cmds.Command{
Status: cmds.Experimental,
Helptext: cmds.HelpText{
Tagline: "Read and outpt log messages.",
Tagline: "Read and output log messages.",
ShortDescription: `
Outputs log messages as they are generated.

Expand All @@ -130,7 +264,7 @@ This will only return 'info' logs from bitswap and skip 'debug'.
var pipeReader *logging.PipeReader
logLevelString, _ := req.Options[logLevelOption].(string)
if logLevelString != "" {
logLevel, err := logging.LevelFromString(logLevelString)
logLevel, err := logging.Parse(logLevelString)
if err != nil {
return fmt.Errorf("setting log level %s: %w", logLevelString, err)
}
Expand Down
23 changes: 22 additions & 1 deletion docs/changelogs/v0.37.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
- [Overview](#overview)
- [🔦 Highlights](#-highlights)
- [Clear provide queue when reprovide strategy changes](#clear-provide-queue-when-reprovide-strategy-changes)
- [Named pins in `ipfs add` command](#-named-pins-in-ipfs-add-command)
- [🪵 Revamped `ipfs log level` command](#-revamped-ipfs-log-level-command)
- [📌 Named pins in `ipfs add` command](#-named-pins-in-ipfs-add-command)
- [⚙️ `Reprovider.Strategy` is now consistently respected](#-reprovider-strategy-is-now-consistently-respected)
- [Removed unnecessary dependencies](#removed-unnecessary-dependencies)
- [Deprecated `ipfs stats reprovide`](#deprecated-ipfs-stats-reprovide)
Expand All @@ -34,6 +35,26 @@ A new `ipfs provide clear` command also allows manual queue clearing for debuggi
> [!NOTE]
> Upgrading to Kubo 0.37 will automatically clear any preexisting provide queue. The next time `Reprovider.Interval` hits, `Reprovider.Strategy` will be executed on a clean slate, ensuring consistent behavior with your current configuration.

#### 🪵 Revamped `ipfs log level` command

The `ipfs log level` command has been completely revamped to support both getting and setting log levels with a unified interface.

**New: Getting log levels**

- `ipfs log level` - Shows default level only
- `ipfs log level all` - Shows log level for every subsystem, including default level
- `ipfs log level foo` - Shows log level for a specific subsystem only
- Kubo RPC API: `POST /api/v0/log/level?arg=<subsystem>`

**Enhanced: Setting log levels**

- `ipfs log level foo debug` - Sets "foo" subsystem to "debug" level
- `ipfs log level all info` - Sets all subsystems to "info" level (convenient, no escaping)
- `ipfs log level '*' info` - Equivalent to above but requires shell escaping
- `ipfs log level foo default` - Sets "foo" subsystem to current default level

The command now provides full visibility into your current logging configuration while maintaining full backward compatibility. Both `all` and `*` work for specifying all subsystems, with `all` being more convenient since it doesn't require shell escaping.

#### 🧷 Named pins in `ipfs add` command

Added `--pin-name` flag to `ipfs add` for assigning names to pins.
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/kubo-as-a-library/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ require (
github.com/ipfs/go-ipld-format v0.6.2 // indirect
github.com/ipfs/go-ipld-git v0.1.1 // indirect
github.com/ipfs/go-ipld-legacy v0.2.2 // indirect
github.com/ipfs/go-log/v2 v2.6.0 // indirect
github.com/ipfs/go-log/v2 v2.8.0 // indirect
github.com/ipfs/go-metrics-interface v0.3.0 // indirect
github.com/ipfs/go-peertaskqueue v0.8.2 // indirect
github.com/ipfs/go-unixfsnode v1.10.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/kubo-as-a-library/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -345,8 +345,8 @@ github.com/ipfs/go-ipld-legacy v0.2.2/go.mod h1:hhkj+b3kG9b2BcUNw8IFYAsfeNo8E3U7
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
github.com/ipfs/go-log/v2 v2.6.0 h1:2Nu1KKQQ2ayonKp4MPo6pXCjqw1ULc9iohRqWV5EYqg=
github.com/ipfs/go-log/v2 v2.6.0/go.mod h1:p+Efr3qaY5YXpx9TX7MoLCSEZX5boSWj9wh86P5HJa8=
github.com/ipfs/go-log/v2 v2.8.0 h1:SptNTPJQV3s5EF4FdrTu/yVdOKfGbDgn1EBZx4til2o=
github.com/ipfs/go-log/v2 v2.8.0/go.mod h1:2LEEhdv8BGubPeSFTyzbqhCqrwqxCbuTNTLWqgNAipo=
github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU=
github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY=
github.com/ipfs/go-peertaskqueue v0.8.2 h1:PaHFRaVFdxQk1Qo3OKiHPYjmmusQy7gKQUaL8JDszAU=
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ require (
github.com/ipfs/go-ipld-format v0.6.2
github.com/ipfs/go-ipld-git v0.1.1
github.com/ipfs/go-ipld-legacy v0.2.2
github.com/ipfs/go-log/v2 v2.6.0
github.com/ipfs/go-log/v2 v2.8.0
github.com/ipfs/go-metrics-interface v0.3.0
github.com/ipfs/go-metrics-prometheus v0.1.0
github.com/ipfs/go-test v0.2.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,8 @@ github.com/ipfs/go-ipld-legacy v0.2.2/go.mod h1:hhkj+b3kG9b2BcUNw8IFYAsfeNo8E3U7
github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM=
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
github.com/ipfs/go-log/v2 v2.6.0 h1:2Nu1KKQQ2ayonKp4MPo6pXCjqw1ULc9iohRqWV5EYqg=
github.com/ipfs/go-log/v2 v2.6.0/go.mod h1:p+Efr3qaY5YXpx9TX7MoLCSEZX5boSWj9wh86P5HJa8=
github.com/ipfs/go-log/v2 v2.8.0 h1:SptNTPJQV3s5EF4FdrTu/yVdOKfGbDgn1EBZx4til2o=
github.com/ipfs/go-log/v2 v2.8.0/go.mod h1:2LEEhdv8BGubPeSFTyzbqhCqrwqxCbuTNTLWqgNAipo=
github.com/ipfs/go-metrics-interface v0.3.0 h1:YwG7/Cy4R94mYDUuwsBfeziJCVm9pBMJ6q/JR9V40TU=
github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY=
github.com/ipfs/go-metrics-prometheus v0.1.0 h1:bApWOHkrH3VTBHzTHrZSfq4n4weOZDzZFxUXv+HyKcA=
Expand Down
Loading
Loading