oslog

package
v0.6.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 8 Imported by: 0

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)
	}
}

Index

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")
}

func NewActivity

func NewActivity(description string) *Activity

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()()

func (*Activity) ID

func (a *Activity) ID() uint64

ID returns the activity's identifier, or 0 if unavailable. It is the value that tags log entries emitted within the activity's scope.

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)
}

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.

func (*Handler) Enabled

func (h *Handler) Enabled(_ context.Context, level slog.Level) bool

Enabled reports whether a record at level would be logged.

func (*Handler) Handle

func (h *Handler) Handle(_ context.Context, r slog.Record) error

Handle renders the record and writes it at the mapped os_log type.

func (*Handler) WithAttrs

func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a Handler whose records carry the given attributes. The attributes are rendered now, under the currently open groups, so later WithGroup calls do not retroactively re-qualify them.

func (*Handler) WithGroup

func (h *Handler) WithGroup(name string) slog.Handler

WithGroup returns a Handler that qualifies subsequent attribute keys with name.

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

func New(subsystem, category string) *Logger

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

func (l *Logger) Debug(format string, args ...any)

Debug logs at OS_LOG_TYPE_DEBUG. Debug messages are the most verbose and are discarded unless explicitly enabled for the subsystem.

func (*Logger) Default

func (l *Logger) Default(format string, args ...any)

Default logs at OS_LOG_TYPE_DEFAULT, the standard level.

func (*Logger) Enabled

func (l *Logger) Enabled(t Type) bool

Enabled reports whether messages of the given type are being recorded.

func (*Logger) Error

func (l *Logger) Error(format string, args ...any)

Error logs at OS_LOG_TYPE_ERROR.

func (*Logger) Fault

func (l *Logger) Fault(format string, args ...any)

Fault logs at OS_LOG_TYPE_FAULT, for a bug in program execution.

func (*Logger) Info

func (l *Logger) Info(format string, args ...any)

Info logs at OS_LOG_TYPE_INFO.

func (*Logger) Log

func (l *Logger) Log(t Type, format string, args ...any)

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

func (l *Logger) LogStack(t Type, format string, args ...any)

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

func (l *Logger) Recover() (recovered any)

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")
}

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()

type Type

type Type uint8

Type is an os_log_type_t: the level a message is logged at.

const (
	TypeDefault Type = 0x00 // OS_LOG_TYPE_DEFAULT
	TypeInfo    Type = 0x01 // OS_LOG_TYPE_INFO
	TypeDebug   Type = 0x02 // OS_LOG_TYPE_DEBUG
	TypeError   Type = 0x10 // OS_LOG_TYPE_ERROR
	TypeFault   Type = 0x11 // OS_LOG_TYPE_FAULT
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL