Documentation
¶
Overview ¶
Package actorevent emits the actor events: the lifecycle events and the usage samples. Log writes each record as an OTLP event when the logs exporter is on, and on stdout otherwise, or in both places when asked to. Both forms are built from the same call, so they cannot drift. Ordinary component logs stay on stdout.
This is not an slog bridge. A bridge would put every component record on the wire, cannot set EventName, and would loop, because serverboot routes OTel SDK errors through slog.
serverboot.InitLogging pairs this with a batching processor. These records sit on the actor resume path, so exporting inside Log would put a blocking gRPC call there and make a slow collector look like control-plane latency.
Index ¶
- Constants
- Variables
- func BuildRecord(ev Event, t time.Time, attrs []slog.Attr) log.Record
- func Log(ctx context.Context, ev Event, attrs []slog.Attr)
- func LogAt(ctx context.Context, ev Event, t time.Time, attrs []slog.Attr)
- func SetConsole(on bool)
- type AsyncHandler
- func (h *AsyncHandler) Close()
- func (h *AsyncHandler) Enabled(ctx context.Context, level slog.Level) bool
- func (h *AsyncHandler) Handle(ctx context.Context, r slog.Record) error
- func (h *AsyncHandler) WithAttrs(attrs []slog.Attr) slog.Handler
- func (h *AsyncHandler) WithGroup(name string) slog.Handler
- type Emitter
- type Event
Constants ¶
const ScopeName = "github.com/agent-substrate/substrate/internal/actorevent"
ScopeName is the instrumentation scope of every actor event. A consumer selects a stream by event name.
Variables ¶
var ( StateChanged = Event{ Name: "ate.actor.state_changed", Body: "Actor state changed", Severity: log.SeverityInfo, Keys: append(append([]string{}, identityKeys...), string(ateattr.ActorOperationNameKey), string(ateattr.ActorStateKey)), } Crashed = Event{ Name: "ate.actor.crashed", Body: "Actor crashed", Severity: log.SeverityError, Keys: append(append([]string{}, identityKeys...), string(ateattr.ActorOperationNameKey), string(ateattr.ActorStateKey)), } UsageSampled = Event{ Name: "ate.actor.usage_sampled", Body: "Actor usage sampled", Severity: log.SeverityInfo, Keys: append(append([]string{}, identityKeys...), string(ateattr.WorkerPoolNamespaceKey), string(ateattr.WorkerPoolNameKey), string(ateattr.SandboxClassKey), string(ateattr.StatsSourceKey), string(ateattr.StatsKindKey), string(ateattr.ActorEpochKey)), Conditional: []string{ string(ateattr.StatsMemoryUsageKey), string(ateattr.StatsMemoryPeakKey), string(ateattr.StatsMemoryWorkingSetKey), string(ateattr.StatsCPUTimeKey), }, } )
Three names. A crash is its own because it has a different severity; there is no name per state, because ate.actor.state already says which transition happened. UsageSampled is the ateoms' measurement record.
Functions ¶
func BuildRecord ¶
BuildRecord turns the stdout record into its OTLP form. Attributes carry everything machine-readable, so the body stays the display string.
It sets no trace context: the SDK lifts that from ctx onto the record's own TraceId/SpanId fields.
func SetConsole ¶ added in v0.4.0
func SetConsole(on bool)
SetConsole sets whether an actor event's stdout record is also written when its OTLP event is: OTEL_LOGS_EXPORTER=otlp,console. Call it once at startup.
Types ¶
type AsyncHandler ¶ added in v0.4.0
type AsyncHandler struct {
// contains filtered or unexported fields
}
AsyncHandler writes records through another handler from its own goroutine, so a slow stdout cannot block the caller. When the queue is full it drops the record and counts it, and the writer reports the count through that same handler once it catches up, so a level that silences the default logger cannot hide the loss.
func NewAsyncHandler ¶ added in v0.4.0
func NewAsyncHandler(inner slog.Handler, capacity int) *AsyncHandler
NewAsyncHandler starts the writer. capacity is how many records may wait.
func (*AsyncHandler) Close ¶ added in v0.4.0
func (h *AsyncHandler) Close()
Close writes the queued records and stops the writer. A record handled after Close is written on the caller's goroutine.
type Emitter ¶
type Emitter struct {
// contains filtered or unexported fields
}
Emitter writes the OTLP event through one log.Logger. Tests construct one directly, so emit needs no global provider and can run in parallel.
func NewEmitter ¶
func NewEmitter(lp log.LoggerProvider) *Emitter
NewEmitter writes under ScopeName, with the stdout record through slog.Default().
func NewEmitterTo ¶ added in v0.4.0
func NewEmitterTo(lp log.LoggerProvider, stdout slog.Handler) *Emitter
NewEmitterTo is NewEmitter with the stdout record through stdout, for a stream whose stdout record must not follow --log-level or block the caller.
func (*Emitter) LogAt ¶ added in v0.3.0
LogAt writes ev as an OTLP event when the logs exporter takes it, and on stdout otherwise, or both with SetConsole(true). t is when the thing happened, such as a usage sample's read time, so the stdout record is built here rather than through slog.LogAttrs, which would take its own reading. With slog.Default() as the stdout handler, it follows --log-level.
type Event ¶
type Event struct {
Name string
Body string
Severity log.Severity
Keys []string
Conditional []string
}
Event is one name in the closed vocabulary. Name is the LogRecord's own event name field, not an attribute. Body and Severity live here rather than at a call site, so the stdout and OTLP forms of a record cannot differ.
Keys is the attribute set the name promises on every record. Conditional holds the keys that are present exactly when the registry's condition holds, and absent otherwise: absence means not measured, zero means measured as zero. The tests hold both lists in step with the registry, and a caller cannot widen the record.