Documentation
¶
Overview ¶
Package oslog writes messages to the unified logging system (os_log).
The os_log family (os_log, os_log_error, os_log_with_type, …) is a set of C macros in <os/log.h>, not exported functions: clang parses the format string at compile time and builds a companion argument buffer via __builtin_os_log_format. Applegen cannot generate bindings for them, so this package is a hand-written overlay. It builds the same argument buffer at run time and calls the underlying exported _os_log_impl symbol through purego, so it needs no cgo.
Unlike the compiler, this package interprets the format string at run time. It supports the os_log format specifiers %d, %u, %x, %ld, %lu, %lx, %p, %s, and %@ (the last logged as %s). Public and private markers (%{public}s, %{private}s) are honored; os_log redacts private arguments unless the consumer is entitled to see them.
Basic usage:
log := oslog.New("com.example.app", "network")
log.Info("connected to %{public}s in %dms", host, elapsed)
log.Error("request failed: %s", err)
Messages appear in Console.app and the `log` command:
log stream --predicate 'subsystem == "com.example.app"'
Example ¶
Log formatted messages to the unified logging system. View them with "log stream --predicate 'subsystem == \"com.example.app\"'" or Console.app.
package main
import (
"errors"
"github.com/tmc/apple/x/oslog"
)
func main() {
log := oslog.New("com.example.app", "network")
host := "api.example.com"
log.Info("connecting to %{public}s", host)
log.Default("received %d bytes in %dms", 4096, 12)
if err := errors.New("timeout"); err != nil {
log.Error("request failed: %{public}s", err)
}
}
Output:
Index ¶
- func Stack() string
- type Activity
- type ActivityFlag
- type Handler
- type HandlerOptions
- type Logger
- func (l *Logger) Debug(format string, args ...any)
- func (l *Logger) Default(format string, args ...any)
- func (l *Logger) Enabled(t Type) bool
- func (l *Logger) Error(format string, args ...any)
- func (l *Logger) Fault(format string, args ...any)
- func (l *Logger) Info(format string, args ...any)
- func (l *Logger) Log(t Type, format string, args ...any)
- func (l *Logger) LogStack(t Type, format string, args ...any)
- func (l *Logger) Recover() (recovered any)
- func (l *Logger) RecoverAndRepanic()
- type Type
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Stack ¶
func Stack() string
Stack returns the current goroutine's stack trace, formatted the way the Go runtime prints it. It is a convenience wrapper around runtime.Stack for including a Go call stack in a log message, since the backtrace os_log captures natively refers to the C/purego call site, not Go frames.
Types ¶
type Activity ¶
type Activity struct {
// contains filtered or unexported fields
}
An Activity groups related log messages under a common identifier. Every os_log message emitted while an activity's scope is active is tagged with the activity's id, so a later reader (Console.app, `log show`, Instruments) can collapse all the work of one logical operation together, even across threads.
Create an activity, enter its scope around the work, and leave it — typically with defer:
act := oslog.NewActivity("handle request")
defer act.Enter()()
log.Info("started") // tagged with act.ID()
Example ¶
Correlate a group of log messages under a single activity so a reader can collapse all the work of one operation together, even across threads.
package main
import (
"github.com/tmc/apple/x/oslog"
)
func main() {
log := oslog.New("com.example.app", "worker")
act := oslog.NewActivity("handle request")
defer act.Enter()() // leave the scope when the function returns
log.Info("started") // both messages are tagged with act.ID()
log.Default("processing")
}
Output:
func NewActivity ¶
NewActivity creates an activity with the given description, nested under the current activity. It never returns nil; if the symbols cannot be resolved the activity's methods are no-ops.
func NewActivityFlags ¶
func NewActivityFlags(description string, flags ActivityFlag) *Activity
NewActivityFlags creates an activity with an explicit flag (for example ActivityDetached to start a new root activity).
func (*Activity) Enter ¶
func (a *Activity) Enter() (leave func())
Enter makes the activity current on the calling goroutine's thread and returns a function that leaves the scope. Because scope enter/leave must happen on the same OS thread, Enter locks the goroutine to its thread until the returned function is called; use it with defer:
defer act.Enter()()
type ActivityFlag ¶
type ActivityFlag uint32
ActivityFlag controls how a new activity relates to the current one.
const ( // ActivityDefault nests the new activity under the current one. ActivityDefault ActivityFlag = 0 // ActivityDetached makes the new activity a root, ignoring any current one. ActivityDetached ActivityFlag = 0x1 // ActivityIfNonePresent creates the activity only if one is not already // current; otherwise the new activity is a no-op that adopts the current. ActivityIfNonePresent ActivityFlag = 0x2 )
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is a slog.Handler that writes records to the unified logging system through a Logger. Levels map to os_log types; the record message and its attributes are rendered into the log message text, since os_log has no named fields. The subsystem and category come from the Logger.
Use it with slog:
log := slog.New(oslog.NewHandler(oslog.New("com.example.app", "worker"), nil))
log.Info("job done", "id", 42, "dur", "1.2s")
Example ¶
Route Go's structured logger (log/slog) to the system log. Levels map to os_log types and attributes render into the message.
package main
import (
"log/slog"
"github.com/tmc/apple/x/oslog"
)
func main() {
logger := oslog.New("com.example.app", "worker")
slog.SetDefault(slog.New(oslog.NewHandler(logger, &oslog.HandlerOptions{
Level: slog.LevelInfo,
})))
slog.Info("job started", "id", 42, "queue", "default")
slog.With("request_id", "abc123").WithGroup("http").
Info("handled", "method", "GET", "status", 200)
}
Output:
func NewHandler ¶
func NewHandler(logger *Logger, opts *HandlerOptions) *Handler
NewHandler returns a Handler writing to logger. A nil opts is treated as the zero HandlerOptions.
type HandlerOptions ¶
type HandlerOptions struct {
// Level reports the minimum record level to log. If nil, slog.LevelInfo.
Level slog.Leveler
// Private redacts attribute values in the system log (they appear as
// <private> unless the reader is entitled to see them). By default a
// Handler logs its message and attributes publicly, since slog attributes
// are developer-chosen structured fields meant to be read back. Set Private
// for a logger that may carry sensitive data.
Private bool
}
HandlerOptions configure a Handler.
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger writes messages to a single os_log handle. Create one with New. It is safe for concurrent use. The zero value is not usable.
func New ¶
New returns a Logger that writes under the given subsystem (reverse-DNS, e.g. "com.example.app") and category. New never returns nil; if the os_log symbols cannot be resolved the Logger's methods are no-ops.
func (*Logger) Debug ¶
Debug logs at OS_LOG_TYPE_DEBUG. Debug messages are the most verbose and are discarded unless explicitly enabled for the subsystem.
func (*Logger) Log ¶
Log writes a formatted message at the given type. The format string uses os_log specifiers, not Go's: %d/%u/%x (and l-prefixed 64-bit forms), %p, %s, and %@ (rendered as %s). Wrap a specifier as %{public}… or %{private}… to set its visibility; the default matches os_log (scalars public, strings private on release builds).
func (*Logger) LogStack ¶
LogStack logs the message at the given type with the current goroutine's Go stack trace appended. The stack is logged as a public string so it is not redacted.
func (*Logger) Recover ¶
Recover recovers a panicking goroutine and logs the panic value and stack at TypeFault, then returns the recovered value (nil if there was no panic). Use it with defer at a goroutine boundary:
func worker(log *oslog.Logger) {
defer log.Recover()
// ... work that might panic ...
}
Recover swallows the panic. To log and then re-panic (preserving a crash), use Logger.RecoverAndRepanic.
Example ¶
Log a panic (with its Go stack) to the system log at Fault level, recovering so the goroutine survives. Defer it at any goroutine boundary.
package main
import (
"github.com/tmc/apple/x/oslog"
)
func main() {
log := oslog.New("com.example.app", "worker")
func() {
defer log.Recover() // logs panic + stack at Fault, then swallows it
panic("unexpected state")
}()
log.Info("recovered, still running")
}
Output:
func (*Logger) RecoverAndRepanic ¶
func (l *Logger) RecoverAndRepanic()
RecoverAndRepanic logs a panic at TypeFault with its stack, then re-panics so the process still crashes (and any outer recover still sees it). Use it when you want the panic recorded in the system log but not suppressed:
defer log.RecoverAndRepanic()