From 4dddab3b6f1fb913f1824ba50781009ba2f085a6 Mon Sep 17 00:00:00 2001 From: Thomas Ankcorn Date: Fri, 20 Mar 2026 17:15:45 +0000 Subject: [PATCH 1/3] Add preview metadata to buffered tail traces --- src/workerd/api/trace.c++ | 16 ++++++++++++++ src/workerd/api/trace.h | 20 ++++++++++++++++++ src/workerd/io/trace-test.c++ | 21 +++++++++++++++++++ src/workerd/io/trace.c++ | 30 +++++++++++++++++++++++++++ src/workerd/io/trace.h | 17 +++++++++++++++ src/workerd/io/worker-interface.capnp | 7 +++++++ 6 files changed, 111 insertions(+) diff --git a/src/workerd/api/trace.c++ b/src/workerd/api/trace.c++ index 7d2c7135b90..b54945ad3e6 100644 --- a/src/workerd/api/trace.c++ +++ b/src/workerd/api/trace.c++ @@ -213,6 +213,7 @@ TraceItem::TraceItem(jsg::Lock& js, const Trace& trace) scriptTags(getTraceScriptTags(trace)), tailAttributes(trace.tailAttributes.map( [](auto& tags) { return KJ_MAP(tag, tags) { return tag.clone(); }; })), + preview(trace.preview.map([](auto& p) { return Preview(p); })), durableObjectId(mapCopyString(trace.durableObjectId)), executionModel(kj::str(trace.executionModel)), outcome(kj::str(trace.outcome)), @@ -306,6 +307,10 @@ jsg::Optional> TraceItem::getTailAttrib }); } +jsg::Optional TraceItem::getPreview() { + return preview; +} + jsg::Optional TraceItem::getDurableObjectId() { return durableObjectId.map([](auto& id) -> kj::StringPtr { return id; }); } @@ -537,6 +542,16 @@ ScriptVersion::ScriptVersion(const ScriptVersion& other) tag{mapCopyString(other.tag)}, message{mapCopyString(other.message)} {} +Preview::Preview(const tracing::TracePreview& preview) + : id(kj::str(preview.id)), + slug(kj::str(preview.slug)), + name(kj::str(preview.name)) {} + +Preview::Preview(const Preview& other) + : id(kj::str(other.id)), + slug(kj::str(other.slug)), + name(kj::str(other.name)) {} + TraceItem::CustomEventInfo::CustomEventInfo( const Trace& trace, const tracing::CustomEventInfo& eventInfo) : eventInfo(eventInfo) {} @@ -789,6 +804,7 @@ void TraceItem::visitForMemoryInfo(jsg::MemoryTracker& tracker) const { } } } + tracker.trackField("preview", preview); tracker.trackField("outcome", outcome); } diff --git a/src/workerd/api/trace.h b/src/workerd/api/trace.h index 7f2a8c537a0..1adf933f7b9 100644 --- a/src/workerd/api/trace.h +++ b/src/workerd/api/trace.h @@ -67,6 +67,23 @@ struct ScriptVersion { } }; +struct Preview { + explicit Preview(const tracing::TracePreview& preview); + Preview(const Preview&); + + kj::String id; + kj::String slug; + kj::String name; + + JSG_STRUCT(id, slug, name); + + JSG_MEMORY_INFO(Preview) { + tracker.trackField("id", id); + tracker.trackField("slug", slug); + tracker.trackField("name", name); + } +}; + class TraceItem final: public jsg::Object { public: using TailAttributeValue = kj::OneOf; @@ -103,6 +120,7 @@ class TraceItem final: public jsg::Object { jsg::Optional getScriptVersion(); jsg::Optional getDispatchNamespace(); jsg::Optional> getScriptTags(); + jsg::Optional getPreview(); jsg::Optional> getTailAttributes(); jsg::Optional getDurableObjectId(); kj::StringPtr getExecutionModel(); @@ -124,6 +142,7 @@ class TraceItem final: public jsg::Object { JSG_LAZY_READONLY_INSTANCE_PROPERTY(dispatchNamespace, getDispatchNamespace); JSG_LAZY_READONLY_INSTANCE_PROPERTY(scriptTags, getScriptTags); JSG_LAZY_READONLY_INSTANCE_PROPERTY(tailAttributes, getTailAttributes); + JSG_LAZY_READONLY_INSTANCE_PROPERTY(preview, getPreview); JSG_LAZY_READONLY_INSTANCE_PROPERTY(durableObjectId, getDurableObjectId); JSG_LAZY_READONLY_INSTANCE_PROPERTY(outcome, getOutcome); JSG_LAZY_READONLY_INSTANCE_PROPERTY(executionModel, getExecutionModel); @@ -146,6 +165,7 @@ class TraceItem final: public jsg::Object { kj::Maybe dispatchNamespace; jsg::Optional> scriptTags; kj::Maybe> tailAttributes; + jsg::Optional preview; kj::Maybe durableObjectId; kj::String executionModel; kj::String outcome; diff --git a/src/workerd/io/trace-test.c++ b/src/workerd/io/trace-test.c++ index 04d04cf8c4e..8c40388dae0 100644 --- a/src/workerd/io/trace-test.c++ +++ b/src/workerd/io/trace-test.c++ @@ -577,12 +577,33 @@ KJ_TEST("Read/Write TailEvent with Multiple Attributes") { KJ_ASSERT(attrs2[1].name == "bar"_kj); } +KJ_TEST("Trace with Preview") { + auto trace = kj::refcounted(kj::str("test-stable-id"), kj::str("test-script"), + kj::none, // scriptVersion + kj::str("test-namespace"), kj::str("test-script-id"), + kj::Array(), // scriptTags + kj::str("test-entrypoint"), ExecutionModel::STATELESS, + TracePreview(kj::str("63bafce9179948688866bb22268eb1c6"), kj::str("feature-my-branch"), + kj::str("feature/my-branch"))); + + capnp::MallocMessageBuilder builder; + auto traceBuilder = builder.initRoot(); + trace->copyTo(traceBuilder); + + auto trace2 = kj::refcounted(traceBuilder.asReader()); + auto& preview = KJ_ASSERT_NONNULL(trace2->preview); + KJ_ASSERT(preview.id == "63bafce9179948688866bb22268eb1c6"_kj); + KJ_ASSERT(preview.slug == "feature-my-branch"_kj); + KJ_ASSERT(preview.name == "feature/my-branch"_kj); +} + KJ_TEST("Trace with Durable Object ID") { auto trace = kj::refcounted(kj::str("test-stable-id"), kj::str("test-script"), kj::none, // scriptVersion kj::str("test-namespace"), kj::str("test-script-id"), kj::Array(), // scriptTags kj::str("test-entrypoint"), ExecutionModel::DURABLE_OBJECT, + kj::none, // preview kj::str("abc123def456") // durableObjectId ); diff --git a/src/workerd/io/trace.c++ b/src/workerd/io/trace.c++ index 08f2734ba7d..95d44056528 100644 --- a/src/workerd/io/trace.c++ +++ b/src/workerd/io/trace.c++ @@ -498,6 +498,26 @@ TraceEventInfo TraceEventInfo::clone() const { return TraceEventInfo(KJ_MAP(item, traces) { return item.clone(); }); } +TracePreview::TracePreview(kj::String id, kj::String slug, kj::String name) + : id(kj::mv(id)), + slug(kj::mv(slug)), + name(kj::mv(name)) {} + +TracePreview::TracePreview(rpc::Trace::Preview::Reader reader) + : id(kj::str(reader.getId())), + slug(kj::str(reader.getSlug())), + name(kj::str(reader.getName())) {} + +void TracePreview::copyTo(rpc::Trace::Preview::Builder builder) const { + builder.setId(id); + builder.setSlug(slug); + builder.setName(name); +} + +TracePreview TracePreview::clone() const { + return TracePreview(kj::str(id), kj::str(slug), kj::str(name)); +} + TraceEventInfo::TraceItem::TraceItem(kj::Maybe scriptName) : scriptName(kj::mv(scriptName)) {} @@ -697,6 +717,7 @@ Trace::Trace(kj::Maybe stableId, kj::Array scriptTags, kj::Maybe entrypoint, ExecutionModel executionModel, + kj::Maybe preview, kj::Maybe durableObjectId) : stableId(kj::mv(stableId)), scriptName(kj::mv(scriptName)), @@ -705,6 +726,7 @@ Trace::Trace(kj::Maybe stableId, scriptId(kj::mv(scriptId)), scriptTags(kj::mv(scriptTags)), entrypoint(kj::mv(entrypoint)), + preview(kj::mv(preview)), durableObjectId(kj::mv(durableObjectId)), executionModel(executionModel) {} Trace::Trace(rpc::Trace::Reader reader) { @@ -764,6 +786,10 @@ void Trace::copyTo(rpc::Trace::Builder builder) const { builder.setEntrypoint(e); } + KJ_IF_SOME(p, preview) { + p.copyTo(builder.initPreview()); + } + KJ_IF_SOME(id, durableObjectId) { builder.setDurableObjectId(id); } @@ -874,6 +900,10 @@ void Trace::mergeFrom(rpc::Trace::Reader reader, PipelineLogLevel pipelineLogLev entrypoint = kj::str(reader.getEntrypoint()); } + if (reader.hasPreview()) { + preview = tracing::TracePreview(reader.getPreview()); + } + if (reader.hasDurableObjectId()) { durableObjectId = kj::str(reader.getDurableObjectId()); } diff --git a/src/workerd/io/trace.h b/src/workerd/io/trace.h index 75aac486182..9e2e1b31a78 100644 --- a/src/workerd/io/trace.h +++ b/src/workerd/io/trace.h @@ -423,6 +423,21 @@ struct EmailEventInfo final { }; // Describes a buffered tail worker request +struct TracePreview final { + explicit TracePreview(kj::String id, kj::String slug, kj::String name); + TracePreview(rpc::Trace::Preview::Reader reader); + TracePreview(TracePreview&&) = default; + TracePreview& operator=(TracePreview&&) = default; + KJ_DISALLOW_COPY(TracePreview); + + kj::String id; + kj::String slug; + kj::String name; + + void copyTo(rpc::Trace::Preview::Builder builder) const; + TracePreview clone() const; +}; + struct TraceEventInfo final { struct TraceItem; @@ -885,6 +900,7 @@ class Trace final: public kj::Refcounted { kj::Array scriptTags, kj::Maybe entrypoint, ExecutionModel executionModel, + kj::Maybe preview = kj::none, kj::Maybe durableObjectId = kj::none); Trace(rpc::Trace::Reader reader); ~Trace() noexcept(false); @@ -908,6 +924,7 @@ class Trace final: public kj::Refcounted { kj::Array scriptTags; kj::Maybe> tailAttributes; kj::Maybe entrypoint; + kj::Maybe preview; kj::Maybe durableObjectId; kj::Vector logs; diff --git a/src/workerd/io/worker-interface.capnp b/src/workerd/io/worker-interface.capnp index 4798b90664f..3dcb02f88fd 100644 --- a/src/workerd/io/worker-interface.capnp +++ b/src/workerd/io/worker-interface.capnp @@ -134,6 +134,12 @@ struct Trace @0x8e8d911203762d34 { rawSize @2 :UInt32; } + struct Preview { + id @0 :Text; + slug @1 :Text; + name @2 :Text; + } + struct TraceEventInfo { struct TraceItem { scriptName @0 :Text; @@ -167,6 +173,7 @@ struct Trace @0x8e8d911203762d34 { scriptTags @14 :List(Text); entrypoint @22 :Text; + preview @29 :Preview; durableObjectId @27 :Text; tailAttributes @28 :List(Attribute); From c848ed4150249d530f2269570a58d4509334897f Mon Sep 17 00:00:00 2001 From: Thomas Ankcorn Date: Wed, 25 Mar 2026 10:49:25 +0000 Subject: [PATCH 2/3] Add preview metadata to streaming tail onset events --- src/workerd/api/trace.h | 3 ++- src/workerd/io/trace-stream.c++ | 9 +++++++++ src/workerd/io/trace-test.c++ | 12 ++++++++++++ src/workerd/io/trace.c++ | 13 +++++++++++++ src/workerd/io/trace.h | 1 + src/workerd/io/tracer.c++ | 1 + src/workerd/io/worker-interface.capnp | 1 + 7 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/workerd/api/trace.h b/src/workerd/api/trace.h index 1adf933f7b9..c2d0d639a8c 100644 --- a/src/workerd/api/trace.h +++ b/src/workerd/api/trace.h @@ -669,7 +669,8 @@ class TraceCustomEvent final: public WorkerInterface::CustomEvent { }; #define EW_TRACE_ISOLATE_TYPES \ - api::ScriptVersion, api::TailEvent, api::TraceItem, api::TraceItem::AlarmEventInfo, \ + api::Preview, api::ScriptVersion, api::TailEvent, api::TraceItem, \ + api::TraceItem::AlarmEventInfo, \ api::TraceItem::CustomEventInfo, api::TraceItem::ScheduledEventInfo, \ api::TraceItem::QueueEventInfo, api::TraceItem::EmailEventInfo, \ api::TraceItem::TailEventInfo, api::TraceItem::TailEventInfo::TailItem, \ diff --git a/src/workerd/io/trace-stream.c++ b/src/workerd/io/trace-stream.c++ index d743c3ac575..5ce519278da 100644 --- a/src/workerd/io/trace-stream.c++ +++ b/src/workerd/io/trace-stream.c++ @@ -61,6 +61,7 @@ namespace { V(OK, "ok") \ V(ONSET, "onset") \ V(OUTCOME, "outcome") \ + V(PREVIEW, "preview") \ V(QUEUE, "queue") \ V(QUEUENAME, "queueName") \ V(RAWSIZE, "rawSize") \ @@ -80,6 +81,7 @@ namespace { V(SPANOPEN, "spanOpen") \ V(STACK, "stack") \ V(STATUSCODE, "statusCode") \ + V(SLUG, "slug") \ V(STREAMDIAGEVENT, "streamDiagEvent") \ V(STREAMDIAGNOSTIC, "streamDiagnostic") \ V(TAG, "tag") \ @@ -359,6 +361,13 @@ jsg::JsValue ToJs(jsg::Lock& js, const Onset& onset, StringCache& cache) { } obj.set(js, SCRIPTVERSION_STR, vobj); } + KJ_IF_SOME(preview, onset.workerInfo.preview) { + auto pobj = js.obj(); + pobj.set(js, ID_STR, js.str(preview.id)); + pobj.set(js, SLUG_STR, js.str(preview.slug)); + pobj.set(js, NAME_STR, js.str(preview.name)); + obj.set(js, PREVIEW_STR, pobj); + } KJ_SWITCH_ONEOF(onset.info) { KJ_CASE_ONEOF(fetch, FetchEventInfo) { diff --git a/src/workerd/io/trace-test.c++ b/src/workerd/io/trace-test.c++ index 8c40388dae0..1eb1c61df6b 100644 --- a/src/workerd/io/trace-test.c++ +++ b/src/workerd/io/trace-test.c++ @@ -481,6 +481,10 @@ KJ_TEST("Read/Write Onset works") { Onset info(staticSpanId, Onset::Info(kj::mv(fetchInfo)), { .scriptName = kj::str("foo"), + .preview = TracePreview( + kj::str("63bafce9179948688866bb22268eb1c6"), + kj::str("feature-my-branch"), + kj::str("feature/my-branch")), }, nullptr); info.copyTo(infoBuilder); @@ -491,12 +495,20 @@ KJ_TEST("Read/Write Onset works") { KJ_ASSERT(fetchInfo2.method == kj::HttpMethod::GET); KJ_ASSERT(fetchInfo2.url == "https://example.com"_kj); KJ_ASSERT(info2.workerInfo.executionModel == ExecutionModel::STATELESS); + auto& preview2 = KJ_ASSERT_NONNULL(info2.workerInfo.preview); + KJ_ASSERT(preview2.id == "63bafce9179948688866bb22268eb1c6"_kj); + KJ_ASSERT(preview2.slug == "feature-my-branch"_kj); + KJ_ASSERT(preview2.name == "feature/my-branch"_kj); Onset info3 = info.clone(); FetchEventInfo& fetchInfo3 = KJ_ASSERT_NONNULL(info3.info.tryGet()); KJ_ASSERT(fetchInfo3.method == kj::HttpMethod::GET); KJ_ASSERT(fetchInfo3.url == "https://example.com"_kj); KJ_ASSERT(info3.workerInfo.executionModel == ExecutionModel::STATELESS); + auto& preview3 = KJ_ASSERT_NONNULL(info3.workerInfo.preview); + KJ_ASSERT(preview3.id == "63bafce9179948688866bb22268eb1c6"_kj); + KJ_ASSERT(preview3.slug == "feature-my-branch"_kj); + KJ_ASSERT(preview3.name == "feature/my-branch"_kj); } KJ_TEST("Read/Write Outcome works") { diff --git a/src/workerd/io/trace.c++ b/src/workerd/io/trace.c++ index 95d44056528..06bca8d1323 100644 --- a/src/workerd/io/trace.c++ +++ b/src/workerd/io/trace.c++ @@ -1242,11 +1242,20 @@ kj::Maybe getEntrypointFromReader(const rpc::Trace::Onset::Reader& r } return kj::none; } + +kj::Maybe getPreviewFromReader(const rpc::Trace::Onset::Reader& reader) { + if (reader.hasPreview()) { + return tracing::TracePreview(reader.getPreview()); + } + return kj::none; +} + Onset::WorkerInfo getWorkerInfoFromReader(const rpc::Trace::Onset::Reader& reader) { return Onset::WorkerInfo{ .executionModel = reader.getExecutionModel(), .scriptName = getScriptNameFromReader(reader), .scriptVersion = getScriptVersionFromReader(reader), + .preview = getPreviewFromReader(reader), .dispatchNamespace = getDispatchNamespaceFromReader(reader), .scriptId = getScriptIdFromReader(reader), .scriptTags = getScriptTagsFromReader(reader), @@ -1292,6 +1301,9 @@ void Onset::copyTo(rpc::Trace::Onset::Builder builder) const { KJ_IF_SOME(e, workerInfo.entrypoint) { builder.setEntryPoint(e); } + KJ_IF_SOME(p, workerInfo.preview) { + p.copyTo(builder.initPreview()); + } auto infoBuilder = builder.initInfo(); writeOnsetInfo(info, infoBuilder); @@ -1306,6 +1318,7 @@ Onset::WorkerInfo Onset::WorkerInfo::clone() const { .executionModel = executionModel, .scriptName = mapCopyString(scriptName), .scriptVersion = scriptVersion.map([](auto& version) { return capnp::clone(*version); }), + .preview = preview.map([](auto& preview) { return preview.clone(); }), .dispatchNamespace = mapCopyString(dispatchNamespace), .scriptId = mapCopyString(scriptId), .scriptTags = diff --git a/src/workerd/io/trace.h b/src/workerd/io/trace.h index 9e2e1b31a78..79e02433429 100644 --- a/src/workerd/io/trace.h +++ b/src/workerd/io/trace.h @@ -772,6 +772,7 @@ struct Onset final { ExecutionModel executionModel = ExecutionModel::STATELESS; kj::Maybe scriptName; kj::Maybe> scriptVersion; + kj::Maybe preview; kj::Maybe dispatchNamespace; kj::Maybe scriptId; kj::Maybe> scriptTags; diff --git a/src/workerd/io/tracer.c++ b/src/workerd/io/tracer.c++ index eb4d9a56228..27746c2ba77 100644 --- a/src/workerd/io/tracer.c++ +++ b/src/workerd/io/tracer.c++ @@ -363,6 +363,7 @@ void WorkerTracer::setEventInfoInternal( trace->scriptVersion.map([](auto& scriptVersion) -> kj::Own { return capnp::clone(*scriptVersion); }), + .preview = trace->preview.map([](auto& preview) { return preview.clone(); }), .dispatchNamespace = mapCopyString(trace->dispatchNamespace), .scriptId = mapCopyString(trace->scriptId), .scriptTags = KJ_MAP(tag, trace->scriptTags) { return kj::str(tag); }, diff --git a/src/workerd/io/worker-interface.capnp b/src/workerd/io/worker-interface.capnp index 3dcb02f88fd..7bb497b2233 100644 --- a/src/workerd/io/worker-interface.capnp +++ b/src/workerd/io/worker-interface.capnp @@ -267,6 +267,7 @@ struct Trace @0x8e8d911203762d34 { scriptId @4 :Text; scriptTags @5 :List(Text); entryPoint @6 :Text; + preview @10 :Preview; struct Info { union { fetch @0 :FetchEventInfo; From 8209557f3b3230a053848c45989c747a29fb8284 Mon Sep 17 00:00:00 2001 From: Thomas Ankcorn Date: Wed, 25 Mar 2026 14:40:35 +0000 Subject: [PATCH 3/3] Keep Trace durableObjectId parameter position stable --- src/workerd/io/trace-test.c++ | 2 +- src/workerd/io/trace.c++ | 4 ++-- src/workerd/io/trace.h | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/workerd/io/trace-test.c++ b/src/workerd/io/trace-test.c++ index 1eb1c61df6b..ac70f73cbfd 100644 --- a/src/workerd/io/trace-test.c++ +++ b/src/workerd/io/trace-test.c++ @@ -595,6 +595,7 @@ KJ_TEST("Trace with Preview") { kj::str("test-namespace"), kj::str("test-script-id"), kj::Array(), // scriptTags kj::str("test-entrypoint"), ExecutionModel::STATELESS, + kj::none, // durableObjectId TracePreview(kj::str("63bafce9179948688866bb22268eb1c6"), kj::str("feature-my-branch"), kj::str("feature/my-branch"))); @@ -615,7 +616,6 @@ KJ_TEST("Trace with Durable Object ID") { kj::str("test-namespace"), kj::str("test-script-id"), kj::Array(), // scriptTags kj::str("test-entrypoint"), ExecutionModel::DURABLE_OBJECT, - kj::none, // preview kj::str("abc123def456") // durableObjectId ); diff --git a/src/workerd/io/trace.c++ b/src/workerd/io/trace.c++ index 06bca8d1323..b040d456029 100644 --- a/src/workerd/io/trace.c++ +++ b/src/workerd/io/trace.c++ @@ -717,8 +717,8 @@ Trace::Trace(kj::Maybe stableId, kj::Array scriptTags, kj::Maybe entrypoint, ExecutionModel executionModel, - kj::Maybe preview, - kj::Maybe durableObjectId) + kj::Maybe durableObjectId, + kj::Maybe preview) : stableId(kj::mv(stableId)), scriptName(kj::mv(scriptName)), scriptVersion(kj::mv(scriptVersion)), diff --git a/src/workerd/io/trace.h b/src/workerd/io/trace.h index 79e02433429..2cde541eb87 100644 --- a/src/workerd/io/trace.h +++ b/src/workerd/io/trace.h @@ -901,8 +901,8 @@ class Trace final: public kj::Refcounted { kj::Array scriptTags, kj::Maybe entrypoint, ExecutionModel executionModel, - kj::Maybe preview = kj::none, - kj::Maybe durableObjectId = kj::none); + kj::Maybe durableObjectId = kj::none, + kj::Maybe preview = kj::none); Trace(rpc::Trace::Reader reader); ~Trace() noexcept(false); KJ_DISALLOW_COPY_AND_MOVE(Trace);