Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
185 changes: 169 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1206,17 +1206,12 @@ feature to quiet things down.
### Disabling warning messages

When `ghw` isn't able to retrieve some information, it may print certain
warning messages to `stderr`. To disable these warnings, simply set the
warning messages to `stderr`. To disable these warnings, set the
`GHW_DISABLE_WARNINGS` environs variable:

```
$ ghwc memory
WARNING:
Could not determine total physical bytes of memory. This may
be due to the host being a virtual machine or container with no
/var/log/syslog file, or the current user may not have necessary
privileges to read the syslog. We are falling back to setting the
total physical amount of memory to the total usable amount of memory
WARN: Could not determine total physical bytes of memory. This may be due to the host being a virtual machine or container with no /var/log/syslog file, or the current user may not have necessary privileges to read the syslog. We are falling back to setting the total physical amount of memory to the total usable amount of memory
memory (24GB physical, 24GB usable)
```

Expand All @@ -1225,25 +1220,183 @@ $ GHW_DISABLE_WARNINGS=1 ghwc memory
memory (24GB physical, 24GB usable)
```

You can disable warning programmatically using the `WithDisableWarnings` option:
You can disable warning programmatically using the `WithDisableWarnings`
modifier:

```go

import (
"github.com/jaypipes/ghw"
)

mem, err := ghw.Memory(ghw.WithDisableWarnings())
```

`WithDisableWarnings` is a alias for the `WithNullAlerter` option, which in turn
leverages the more general `Alerter` feature of ghw.
### Controlling log output

The default log output in `ghw` only writes WARN-level messages to `stderr` in
a simple `WARN: <msg>` log record format:

```
$ ghwc baseboard
WARN: Unable to read board_serial: open /sys/class/dmi/id/board_serial: permission denied
baseboard vendor=System76 version=thelio-mira-b4.1 product=Thelio Mira
```

You can control a number of log output options programmatically or by using
environs variables.

#### Change the log level

To change the log level `ghw` uses, set the `GHW_LOG_LEVEL` environs variable:

```
$ GHW_LOG_LEVEL=debug ghwc baseboard
DEBUG: reading from "/sys/class/dmi/id/board_asset_tag"
DEBUG: reading from "/sys/class/dmi/id/board_serial"
WARN: Unable to read board_serial: open /sys/class/dmi/id/board_serial: permission denied
DEBUG: reading from "/sys/class/dmi/id/board_vendor"
DEBUG: reading from "/sys/class/dmi/id/board_version"
DEBUG: reading from "/sys/class/dmi/id/board_name"
baseboard vendor=System76 version=thelio-mira-b4.1 product=Thelio Mira
```

Changing `GHW_LOG_LEVEL` to `error` has the same effect of setting
`GHW_DISABLE_WARNINGS`:

```
$ GHW_LOG_LEVEL=error ghwc baseboard
baseboard vendor=System76 version=thelio-mira-b4.1 product=Thelio Mira
```

You can change the log level programmatically using the `WithLogLevel`
modifier:

```go
import (
"log/slog"

"github.com/jaypipes/ghw"
)

bb, err := ghw.Baseboard(ghw.WithLogLevel(slog.LevelDebug))
```

#### Use logfmt output format

To use the [logfmt][logfmt] standard log output format, set the
`GHW_LOG_LOGFMT` envrions variable:

```
$ GHW_LOG_LOGFMT=1 ghwc baseboard
time=2025-12-28T07:31:08.614-05:00 level=WARN msg="Unable to read board_serial: open /sys/class/dmi/id/board_serial: permission denied"
baseboard vendor=System76 version=thelio-mira-b4.1 product=Thelio Mira
```

You can tell `ghw` to use `logfmt` standard output formatting using the `WithLogLogfmt`
modifier:

```go
import (
"log/slog"

"github.com/jaypipes/ghw"
)

bb, err := ghw.Baseboard(ghw.WithLogLogfmt())
```

[logfmt]: https://www.cloudbees.com/blog/logfmt-a-log-format-thats-easy-to-read-and-write

#### Provide a custom logger

You can programmatically override the logger that `ghw` uses with the
`WithLogger` modifier. You pass in an instance of `slog.Logger`, like this
example that shows how to use a simple logger with colored log output:

```go
package main

import (
"context"
"encoding/json"
"io"
"log"
"log/slog"

"github.com/fatih/color"
"github.com/jaypipes/ghw"
)

type PrettyHandlerOptions struct {
SlogOpts slog.HandlerOptions
}

type PrettyHandler struct {
slog.Handler
l *log.Logger
}

func (h *PrettyHandler) Handle(ctx context.Context, r slog.Record) error {
level := r.Level.String() + ":"

switch r.Level {
case slog.LevelDebug:
level = color.MagentaString(level)
case slog.LevelInfo:
level = color.BlueString(level)
case slog.LevelWarn:
level = color.YellowString(level)
case slog.LevelError:
level = color.RedString(level)
}

fields := make(map[string]interface{}, r.NumAttrs())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: map[string]any (please do NOT resubmit just for this)

r.Attrs(func(a slog.Attr) bool {
fields[a.Key] = a.Value.Any()

You may supply a `Alerter` to ghw to redirect all the warnings there, like
logger objects (see for example golang's stdlib `log.Logger`).
`Alerter` is in fact the minimal logging interface `ghw needs.
To learn more, please check the `option.Alerter` interface and the `ghw.WithAlerter()`
function.
return true
})

b, err := json.MarshalIndent(fields, "", " ")
if err != nil {
return err
}

timeStr := r.Time.Format("[15:05:05.000]")
msg := color.CyanString(r.Message)

h.l.Println(timeStr, level, msg, color.WhiteString(string(b)))

return nil
}

func NewPrettyHandler(
out io.Writer,
opts PrettyHandlerOptions,
) *PrettyHandler {
h := &PrettyHandler{
Handler: slog.NewJSONHandler(out, &opts.SlogOpts),
l: log.New(out, "", 0),
}

return h
}

func main() {
opts := PrettyHandlerOptions{
SlogOpts: slog.HandlerOptions{
Level: slog.LevelDebug,
},
}
handler := NewPrettyHandler(os.Stdout, opts)
logger := slog.New(handler)
bb, err := ghw.Baseboard(ghw.WithLogger(logger))
if err != nil {
logger.Error(err.String())
}
fmt.Println(bb)
}
```

### Overriding the root mountpoint `ghw` uses

Expand Down
20 changes: 14 additions & 6 deletions alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/jaypipes/ghw/pkg/bios"
"github.com/jaypipes/ghw/pkg/block"
"github.com/jaypipes/ghw/pkg/chassis"
ghwcontext "github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/cpu"
"github.com/jaypipes/ghw/pkg/gpu"
"github.com/jaypipes/ghw/pkg/memory"
Expand All @@ -26,19 +27,26 @@ import (

// DEPRECATED: Please use Option
type WithOption = option.Option

// DEPRECATED: Please use WithXXX functions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit puzzling because 3 lines above we instruct users to NOT use a WithXXX function

type Option = option.Option

var (
WithChroot = option.WithChroot
WithAlerter = option.WithAlerter
WithChroot = ghwcontext.WithChroot
// DEPRECATED: Please use WithLogger
WithAlerter = option.WithAlerter
// DEPRECATED: Please use WithDisableWarnings
WithNullAlerter = option.WithNullAlerter
// match the existing environ variable to minimize surprises
WithDisableWarnings = option.WithNullAlerter
WithDisableTools = option.WithDisableTools
WithPathOverrides = option.WithPathOverrides
WithDisableWarnings = ghwcontext.WithDisableWarnings
WithDisableTools = ghwcontext.WithDisableTools
WithPathOverrides = ghwcontext.WithPathOverrides
WithLogLevel = ghwcontext.WithLogLevel
WithDebug = ghwcontext.WithDebug
WithLogger = ghwcontext.WithLogger
)

type PathOverrides = option.PathOverrides
type PathOverrides map[string]string

type CPUInfo = cpu.Info

Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/accelerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ var acceleratorCmd = &cobra.Command{

// showAccelerator show processing accelerators information for the host system.
func showAccelerator(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
accel, err := ghw.Accelerator(opts...)
accel, err := ghw.Accelerator(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting Accelerator info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/baseboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ var baseboardCmd = &cobra.Command{

// showBaseboard shows baseboard information for the host system.
func showBaseboard(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
baseboard, err := ghw.Baseboard(opts...)
baseboard, err := ghw.Baseboard(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting baseboard info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/bios.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ var biosCmd = &cobra.Command{

// showBIOS shows BIOS host system.
func showBIOS(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
bios, err := ghw.BIOS(opts...)
bios, err := ghw.BIOS(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting BIOS info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ var blockCmd = &cobra.Command{

// showBlock show block storage information for the host system.
func showBlock(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
block, err := ghw.Block(opts...)
block, err := ghw.Block(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting block device info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/chassis.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ var chassisCmd = &cobra.Command{

// showChassis shows chassis information for the host system.
func showChassis(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
chassis, err := ghw.Chassis(opts...)
chassis, err := ghw.Chassis(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting chassis info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ var cpuCmd = &cobra.Command{

// showCPU show CPU information for the host system.
func showCPU(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
cpu, err := ghw.CPU(opts...)
cpu, err := ghw.CPU(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting CPU info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/gpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ var gpuCmd = &cobra.Command{

// showGPU show graphics/GPU information for the host system.
func showGPU(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
gpu, err := ghw.GPU(opts...)
gpu, err := ghw.GPU(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting GPU info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ var memoryCmd = &cobra.Command{

// showMemory show memory information for the host system.
func showMemory(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
mem, err := ghw.Memory(opts...)
mem, err := ghw.Memory(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting memory info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ var netCmd = &cobra.Command{

// showNetwork show network information for the host system.
func showNetwork(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
net, err := ghw.Network(opts...)
net, err := ghw.Network(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting network info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/pci.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ var pciCmd = &cobra.Command{

// showPCI shows information for PCI devices on the host system.
func showPCI(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
pci, err := ghw.PCI(opts...)
pci, err := ghw.PCI(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting PCI info")
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/ghwc/commands/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ var productCmd = &cobra.Command{

// showProduct shows product information for the host system.
func showProduct(cmd *cobra.Command, args []string) error {
opts := cmd.Context().Value(optsKey).([]ghw.Option)
product, err := ghw.Product(opts...)
product, err := ghw.Product(cmd.Context())
if err != nil {
return errors.Wrap(err, "error getting product info")
}
Expand Down
Loading