Repository navigation
(feat): Actor lifecycle events over otlp - #1658
Jeff Luo (JeffLuoo) merged 3 commits into
Conversation
04d1200 to
c06911f
Compare
|
Jeff Luo (@JeffLuoo), can you please take a look at this when you have some free cycles? |
|
Sure! |
|
Can you first create a bug to track the work and general discussion? |
Sure, wanted to open one for related topics, but I think we can keep these reviewed in parallel as this PR
Opened the discussion here: #1683 |
|
|
||
| kvs := make([]log.KeyValue, 0, len(attrs)) | ||
| for _, a := range attrs { | ||
| kvs = append(kvs, log.String(a.Key, a.Value.String())) |
There was a problem hiding this comment.
log.String(a.Key, a.Value.String()) is lossless today, because ActorLogAttrs and FailureLogAttrs build only slog.String. The Keys test pins the key names, not the value kinds. So the first slog.Int or slog.Duration attribute gives a number on stdout and a string over OTLP, and no test fails. The package doc says the two copies cannot drift, and this is the one way they can. A type switch, or a test that asserts every attribute is string-kind, would help it.
There was a problem hiding this comment.
Thanks, good catch, this is now addressed.
| # The count pipelines read the same receiver as the two pipelines | ||
| # above, and they only count. The connector declares MutatesData | ||
| # false, thus it changes nothing that reaches Jaeger or Prometheus. | ||
| # The actor lifecycle events. debug is the only exporter: kind has no |
There was a problem hiding this comment.
nit - "actor lifecycle events" is what we export today but we expect to have more events coming from the system. Let's make it general like "ate events" or something similar.
There was a problem hiding this comment.
Done.
| # useful of the two anyway. | ||
| logs: | ||
| substrate_actor_state_changes: | ||
| description: Actor state change events received, by state. | ||
| attributes: | ||
| - key: service.name | ||
| default_value: unknown | ||
| - key: ate.actor.state | ||
| default_value: unknown | ||
|
|
There was a problem hiding this comment.
This reads the bare otlp receiver with no filter, and the connector at line 88 defaults ate.actor.state to unknown. The same collector doc says the logs pipeline also serves user workloads, so a demo actor that emits OTLP logs lands in substrate_actor_state_changes{ate_actor_state="unknown"}. The e2e is unaffected, because it reads named states. Worth a filter on the scope, or a description at line 84 that says what is actually counted. If we want to just track state change count, add a filter here.
There was a problem hiding this comment.
This is now fixed by just dropping default_value on ate.actor.state. The connector drops items missing a key that has no default, so we don't even need a processor here. A real event with state unknown still has the key so that still counts.
| LogsExporterNone LogsExporter = "none" | ||
| // LogsExporterOTLP sends to OTEL_EXPORTER_OTLP_ENDPOINT. | ||
| LogsExporterOTLP LogsExporter = "otlp" | ||
| ) |
There was a problem hiding this comment.
Could we accept console here as a third value? Just easier to test change.. Or are you intentional not adding it?
There was a problem hiding this comment.
I intentionally left it out. We already write the same record to stdout so the console version would print the same thing in slightly different format which I think is more confusing than useful here.
As per seeing the OTLP format, the collector's debug exporter already dumps the full record, so unit tests can assert that directly.
There was a problem hiding this comment.
As per seeing the OTLP format, the collector's debug exporter already dumps the full record, so unit tests can assert that directly.
I want to break it down into 2 parts:
-
Instrumentation using OTel SDK for logs / events - Yes. And we should make it freely configurable, including options to write logs to console (stdout).
-
Collection part: We shouldn't assume a collector will be deployed. Users can use any collection agent.
Hence my point is we shouldn't limit the configuration of OTel SDK, and provide users guidance to configure.
There was a problem hiding this comment.
stdout already has the same information in ate.* format in JSON that is readable and parsable by any agent/collector. Adding console will result in the exact same data, duplicated in a slightly different format.
There was a problem hiding this comment.
Thanks, I agree that a console exporter is not needed here: the existing structured stdout record remains available to kubectl logs and to a future filelog collector, while OTLP provides the schema-specific event form.
I do want to make one long-term invariant explicit: one lifecycle event should be constructed once, then projected to stdout and OTLP, rather than maintained as two parallel emissions at each call site. Reusing []slog.Attr is a good start, but slog.LogAttrs(...) and actorevent.Emit(...) still leave separate body/severity/emission paths that can drift as the event evolves.
Would it make sense to have actorevent own a single higher-level emission helper or canonical event record and derive both outputs from it? This is not a blocker for this PR, but I would like us to capture the invariant in the design or a follow-up issue so it does not get lost.
There was a problem hiding this comment.
Sure, opened it here, can pick it up as a follow-up once this one is merged: #1744
5f7c690
What this does
ateapi writes a record every time an actor changes state. Until now those records only went to the pod's stdout, and nothing reads stdout. This sends the same records to a collector as OTLP log events.
Actor name and uid cannot be metric labels (too many values), and traces are sampled at 1%. So these records are the only way to answer "what state is this actor in, and since when".
Changes
serverboot.InitLoggingsets up a LoggerProvider, next to the existing tracer and meter ones.internal/actoreventpackage builds the log records.ate.actor.state_changedandate.actor.crashed.docs/metrics/registry/events.yaml, somake verifychecks them.otel-collector.mdsaid substrate has no LoggerProvider, which is no longer true.Opt-in
OTEL_LOGS_EXPORTERdefaults tonone. Only the kind overlay sets it tootlp. The base ConfigMap is untouched, so no deployed environment changes when this merges.Notes on the design
ate.actor.statealready says which transition happened. A crash gets its own name because it carries two extra attributes and a higher severity.kubectl logskeeps working. If a filelog agent is ever added, drop one of the two. The escape hatch is written down indocs/metrics/substrate.yaml.Dependencies
Adds
otel/log,otel/sdk/logandotlploggrpc, all pinned at v0.20.0. That is the release that matches the pinnedotel v1.44.0. v0.21.0 would pull the core modules to v1.45.0, which this change does not need. The logs API has av1.47.0 release candidate upstream, so it is on its way to stable.
Testing
Unit tests for the exporter resolver, the record builder, and both ateapi emit sites.
The record builder test checks the attribute set matches what the event name declares, in both directions.
The ateapi tests check the OTLP record carries the same attributes as the stdout record.
Ran end to end on kind. Records arrive with the right event name, severity, attributes, and with trace context on the record's own fields rather than as attributes.
Checked the off state too. With
OTEL_LOGS_EXPORTERremoved, the collector receives no log records and stdout is unchanged.Tests pass
Appropriate changes to documentation are included in the PR