// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #pragma once /** * Bindings for trace worker (ie. to support running wrangler tail). */ #include #include #include #include #include namespace workerd::api { class TraceItem; class TraceException; class TraceLog; class TraceDiagnosticChannelEvent; class TailEvent final: public ExtendableEvent { public: explicit TailEvent( jsg::Lock& js, kj::LiteralStringConst type, kj::ArrayPtr> events); static jsg::Ref constructor(kj::String type) = delete; // TODO(soon): constructor? kj::Array> getEvents(); JSG_RESOURCE_TYPE(TailEvent) { JSG_INHERIT(ExtendableEvent); JSG_LAZY_READONLY_INSTANCE_PROPERTY(events, getEvents); // Deprecated. Please, use `events` instead. JSG_LAZY_READONLY_INSTANCE_PROPERTY(traces, getEvents); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const; private: kj::Array> events; void visitForGc(jsg::GcVisitor& visitor) { visitor.visitAll(events); } }; struct ScriptVersion { explicit ScriptVersion(workerd::ScriptVersion::Reader version); ScriptVersion(const ScriptVersion&); jsg::Optional id; jsg::Optional tag; jsg::Optional message; JSG_STRUCT(id, tag, message); JSG_MEMORY_INFO(ScriptVersion) { tracker.trackField("id", id); tracker.trackField("tag", tag); tracker.trackField("message", message); } }; struct TracePreviewInfo { explicit TracePreviewInfo(const tracing::TracePreview& preview); TracePreviewInfo(const TracePreviewInfo&); kj::String id; kj::String slug; kj::String name; JSG_STRUCT(id, slug, name); JSG_MEMORY_INFO(TracePreviewInfo) { tracker.trackField("id", id); tracker.trackField("slug", slug); tracker.trackField("name", name); } }; class TraceItem final: public jsg::Object { public: using TailAttributeValue = kj::OneOf; class FetchEventInfo; class JsRpcEventInfo; class ConnectEventInfo; class ScheduledEventInfo; class AlarmEventInfo; class QueueEventInfo; class EmailEventInfo; class TailEventInfo; class HibernatableWebSocketEventInfo; class CustomEventInfo; explicit TraceItem(jsg::Lock& js, const Trace& trace); using EventInfo = kj::OneOf, jsg::Ref, jsg::Ref, jsg::Ref, jsg::Ref, jsg::Ref, jsg::Ref, jsg::Ref, jsg::Ref, jsg::Ref>; kj::Maybe getEvent(jsg::Lock& js); kj::Maybe getEventTimestamp(); kj::ArrayPtr> getLogs(); kj::ArrayPtr> getExceptions(); kj::ArrayPtr> getDiagnosticChannelEvents(); kj::Maybe getScriptName(); jsg::Optional getEntrypoint(); jsg::Optional getScriptVersion(); jsg::Optional getDispatchNamespace(); jsg::Optional> getScriptTags(); jsg::Optional getPreview(); jsg::Optional> getTailAttributes(); jsg::Optional getDurableObjectId(); kj::StringPtr getExecutionModel(); kj::StringPtr getOutcome(); uint getCpuTime(); uint getWallTime(); bool getTruncated(); JSG_RESOURCE_TYPE(TraceItem, CompatibilityFlags::Reader flags) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(event, getEvent); JSG_LAZY_READONLY_INSTANCE_PROPERTY(eventTimestamp, getEventTimestamp); JSG_LAZY_READONLY_INSTANCE_PROPERTY(logs, getLogs); JSG_LAZY_READONLY_INSTANCE_PROPERTY(exceptions, getExceptions); JSG_LAZY_READONLY_INSTANCE_PROPERTY(diagnosticsChannelEvents, getDiagnosticChannelEvents); JSG_LAZY_READONLY_INSTANCE_PROPERTY(scriptName, getScriptName); JSG_LAZY_READONLY_INSTANCE_PROPERTY(entrypoint, getEntrypoint); JSG_LAZY_READONLY_INSTANCE_PROPERTY(scriptVersion, getScriptVersion); 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); JSG_LAZY_READONLY_INSTANCE_PROPERTY(truncated, getTruncated); JSG_LAZY_READONLY_INSTANCE_PROPERTY(cpuTime, getCpuTime); JSG_LAZY_READONLY_INSTANCE_PROPERTY(wallTime, getWallTime); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const; private: kj::Maybe eventInfo; kj::Maybe eventTimestamp; kj::Array> logs; kj::Array> exceptions; kj::Array> diagnosticChannelEvents; kj::Maybe scriptName; kj::Maybe entrypoint; kj::Maybe scriptVersion; kj::Maybe dispatchNamespace; jsg::Optional> scriptTags; kj::Maybe> tailAttributes; jsg::Optional preview; kj::Maybe durableObjectId; kj::String executionModel; kj::String outcome; uint cpuTime; uint wallTime; bool truncated; }; // When adding a new TraceItem eventInfo type, it is important not to // try keeping a reference to the Trace and tracing::*EventInfo inputs. // They are kj heap objects that have a lifespan that is managed independently // of the TraceItem object. Each of the implementations here extract the // necessary detail on creation and use LAZY instance properties to minimize // copying and allocation necessary when accessing these values. // TODO(cleanup): Later we can further optimize by creating the JS objects // immediately on creation. // While this class is named FetchEventInfo, it encapsulates both the actual // FetchEventInfo as well as the FetchResponseInfo, which is an (optional) // sibling field (see worker.capnp). The internal FetchEventInfo (and // EventInfo in general) only represents the original event, not any // subsequent results such as the HTTP response. Internally, FetchEventInfo is // populated as soon as a request comes in, whereas the FetchResponseInfo is // only set once the request has finished entirely (along with the outcome, // see TraceItem::getOutcome). class TraceItem::FetchEventInfo final: public jsg::Object { public: class Request; class Response; explicit FetchEventInfo(jsg::Lock& js, const Trace& trace, const tracing::FetchEventInfo& eventInfo, kj::Maybe responseInfo); jsg::Ref getRequest(); jsg::Optional> getResponse(); // TODO(cleanup) Use struct types more? JSG_RESOURCE_TYPE(FetchEventInfo) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(response, getResponse); JSG_LAZY_READONLY_INSTANCE_PROPERTY(request, getRequest); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const; private: jsg::Ref request; jsg::Optional> response; }; class TraceItem::FetchEventInfo::Request final: public jsg::Object { public: struct Detail: public kj::Refcounted { jsg::Optional> cf; kj::Array headers; kj::String method; kj::String url; Detail(jsg::Optional> cf, kj::Array headers, kj::String method, kj::String url); JSG_MEMORY_INFO(Detail) { tracker.trackField("cf", cf); for (const auto& header: headers) { tracker.trackField(nullptr, header); } tracker.trackField("method", method); tracker.trackField("url", url); } }; explicit Request(jsg::Lock& js, const Trace& trace, const tracing::FetchEventInfo& eventInfo); // Creates a possibly unredacted instance that shared a ref of the Detail explicit Request(Detail& detail, bool redacted = true); jsg::Optional> getCf(jsg::Lock& js); jsg::Dict getHeaders(jsg::Lock& js); kj::StringPtr getMethod(); kj::String getUrl(); jsg::Ref getUnredacted(jsg::Lock& js); JSG_RESOURCE_TYPE(Request) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(cf, getCf); JSG_LAZY_READONLY_INSTANCE_PROPERTY(headers, getHeaders); JSG_LAZY_READONLY_INSTANCE_PROPERTY(method, getMethod); JSG_LAZY_READONLY_INSTANCE_PROPERTY(url, getUrl); JSG_METHOD(getUnredacted); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("detail", detail); } private: bool redacted = true; kj::Own detail; }; class TraceItem::FetchEventInfo::Response final: public jsg::Object { public: explicit Response(const Trace& trace, const tracing::FetchResponseInfo& responseInfo); uint16_t getStatus(); JSG_RESOURCE_TYPE(Response) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(status, getStatus); } private: uint16_t status; }; class TraceItem::JsRpcEventInfo final: public jsg::Object { public: explicit JsRpcEventInfo(const Trace& trace, const tracing::JsRpcEventInfo& eventInfo); // We call this `rpcMethod` to make clear this is an RPC event, since some tail workers rely // on duck-typing EventInfo based on the properties present. (`methodName` might be ambiguous // since HTTP also has methods.) // // TODO(someday): Clearly there should be a better way to distinguish event types? kj::StringPtr getRpcMethod(); JSG_RESOURCE_TYPE(JsRpcEventInfo) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(rpcMethod, getRpcMethod); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("rpcMethod", rpcMethod); } private: kj::String rpcMethod; }; class TraceItem::ConnectEventInfo final: public jsg::Object { public: explicit ConnectEventInfo( jsg::Lock& js, const Trace& trace, const tracing::ConnectEventInfo& eventInfo); JSG_RESOURCE_TYPE(ConnectEventInfo) {} }; class TraceItem::ScheduledEventInfo final: public jsg::Object { public: explicit ScheduledEventInfo(const Trace& trace, const tracing::ScheduledEventInfo& eventInfo); double getScheduledTime(); kj::StringPtr getCron(); JSG_RESOURCE_TYPE(ScheduledEventInfo) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(scheduledTime, getScheduledTime); JSG_LAZY_READONLY_INSTANCE_PROPERTY(cron, getCron); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("cron", cron); } private: double scheduledTime; kj::String cron; }; class TraceItem::AlarmEventInfo final: public jsg::Object { public: explicit AlarmEventInfo(const Trace& trace, const tracing::AlarmEventInfo& eventInfo); kj::Date getScheduledTime(); JSG_RESOURCE_TYPE(AlarmEventInfo) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(scheduledTime, getScheduledTime); } private: kj::Date scheduledTime; }; class TraceItem::QueueEventInfo final: public jsg::Object { public: explicit QueueEventInfo(const Trace& trace, const tracing::QueueEventInfo& eventInfo); kj::StringPtr getQueueName(); uint32_t getBatchSize(); // TODO(now): Add something about the timestamp(s) of the newest/oldest message(s) in the batch? JSG_RESOURCE_TYPE(QueueEventInfo) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(queue, getQueueName); JSG_LAZY_READONLY_INSTANCE_PROPERTY(batchSize, getBatchSize); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("queueName", queueName); } private: kj::String queueName; uint32_t batchSize; }; class TraceItem::EmailEventInfo final: public jsg::Object { public: explicit EmailEventInfo(const Trace& trace, const tracing::EmailEventInfo& eventInfo); kj::StringPtr getMailFrom(); kj::StringPtr getRcptTo(); uint32_t getRawSize(); JSG_RESOURCE_TYPE(EmailEventInfo) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(mailFrom, getMailFrom); JSG_LAZY_READONLY_INSTANCE_PROPERTY(rcptTo, getRcptTo); JSG_LAZY_READONLY_INSTANCE_PROPERTY(rawSize, getRawSize); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("mailFrom", mailFrom); tracker.trackField("rcptTo", rcptTo); } private: kj::String mailFrom; kj::String rcptTo; uint32_t rawSize; }; class TraceItem::TailEventInfo final: public jsg::Object { public: class TailItem; explicit TailEventInfo( jsg::Lock& js, const Trace& trace, const tracing::TraceEventInfo& eventInfo); kj::Array> getConsumedEvents(); JSG_RESOURCE_TYPE(TailEventInfo) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(consumedEvents, getConsumedEvents); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const; private: kj::Array> consumedEvents; }; class TraceItem::TailEventInfo::TailItem final: public jsg::Object { public: explicit TailItem(const tracing::TraceEventInfo::TraceItem& traceItem); kj::Maybe getScriptName(); JSG_RESOURCE_TYPE(TailItem) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(scriptName, getScriptName); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("scriptName", scriptName); } private: kj::Maybe scriptName; }; class TraceItem::HibernatableWebSocketEventInfo final: public jsg::Object { public: class Message; class Close; class Error; explicit HibernatableWebSocketEventInfo(jsg::Lock& js, const Trace& trace, tracing::HibernatableWebSocketEventInfo::Message eventInfo); explicit HibernatableWebSocketEventInfo( jsg::Lock& js, const Trace& trace, tracing::HibernatableWebSocketEventInfo::Close eventInfo); explicit HibernatableWebSocketEventInfo( jsg::Lock& js, const Trace& trace, tracing::HibernatableWebSocketEventInfo::Error eventInfo); using Type = kj::OneOf, jsg::Ref, jsg::Ref>; Type getEvent(); JSG_RESOURCE_TYPE(HibernatableWebSocketEventInfo) { JSG_READONLY_INSTANCE_PROPERTY(getWebSocketEvent, getEvent); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const; private: Type eventType; }; class TraceItem::HibernatableWebSocketEventInfo::Message final: public jsg::Object { public: explicit Message(const Trace& trace, tracing::HibernatableWebSocketEventInfo::Message eventInfo) : eventInfo(eventInfo) {} static constexpr kj::StringPtr webSocketEventType = "message"_kj; kj::StringPtr getWebSocketEventType() { return webSocketEventType; } JSG_RESOURCE_TYPE(Message) { JSG_READONLY_INSTANCE_PROPERTY(webSocketEventType, getWebSocketEventType); } private: const tracing::HibernatableWebSocketEventInfo::Message eventInfo; }; class TraceItem::HibernatableWebSocketEventInfo::Close final: public jsg::Object { public: explicit Close(const Trace& trace, tracing::HibernatableWebSocketEventInfo::Close eventInfo) : eventInfo(eventInfo) {} static constexpr kj::StringPtr webSocketEventType = "close"_kj; kj::StringPtr getWebSocketEventType() { return webSocketEventType; } uint16_t getCode(); bool getWasClean(); JSG_RESOURCE_TYPE(Close) { JSG_READONLY_INSTANCE_PROPERTY(webSocketEventType, getWebSocketEventType); JSG_READONLY_INSTANCE_PROPERTY(code, getCode); JSG_READONLY_INSTANCE_PROPERTY(wasClean, getWasClean); } private: const tracing::HibernatableWebSocketEventInfo::Close eventInfo; }; class TraceItem::HibernatableWebSocketEventInfo::Error final: public jsg::Object { public: explicit Error(const Trace& trace, tracing::HibernatableWebSocketEventInfo::Error eventInfo) : eventInfo(eventInfo) {} static constexpr kj::StringPtr webSocketEventType = "error"_kj; kj::StringPtr getWebSocketEventType() { return webSocketEventType; } JSG_RESOURCE_TYPE(Error) { JSG_READONLY_INSTANCE_PROPERTY(webSocketEventType, getWebSocketEventType); } private: const tracing::HibernatableWebSocketEventInfo::Error eventInfo; }; class TraceItem::CustomEventInfo final: public jsg::Object { public: explicit CustomEventInfo(const Trace& trace, const tracing::CustomEventInfo& eventInfo); JSG_RESOURCE_TYPE(CustomEventInfo) {} private: const tracing::CustomEventInfo& eventInfo; }; class TraceDiagnosticChannelEvent final: public jsg::Object { public: explicit TraceDiagnosticChannelEvent( const Trace& trace, const tracing::DiagnosticChannelEvent& eventInfo); double getTimestamp(); kj::StringPtr getChannel(); jsg::JsValue getMessage(jsg::Lock& js); JSG_RESOURCE_TYPE(TraceDiagnosticChannelEvent) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(timestamp, getTimestamp); JSG_LAZY_READONLY_INSTANCE_PROPERTY(channel, getChannel); JSG_LAZY_READONLY_INSTANCE_PROPERTY(message, getMessage); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("channel", channel); tracker.trackFieldWithSize("message", message.size()); } private: double timestamp; kj::String channel; kj::Array message; }; class TraceLog final: public jsg::Object { public: TraceLog(jsg::Lock& js, const Trace& trace, const tracing::Log& log); double getTimestamp(); kj::StringPtr getLevel(); jsg::V8Ref getMessage(jsg::Lock& js); JSG_RESOURCE_TYPE(TraceLog) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(timestamp, getTimestamp); JSG_LAZY_READONLY_INSTANCE_PROPERTY(level, getLevel); JSG_LAZY_READONLY_INSTANCE_PROPERTY(message, getMessage); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("message", message); } private: double timestamp; kj::LiteralStringConst level; jsg::V8Ref message; }; class TraceException final: public jsg::Object { public: TraceException(const Trace& trace, const tracing::Exception& exception); double getTimestamp(); kj::StringPtr getName(); kj::StringPtr getMessage(); jsg::Optional getStack(jsg::Lock& js); JSG_RESOURCE_TYPE(TraceException) { JSG_LAZY_READONLY_INSTANCE_PROPERTY(timestamp, getTimestamp); JSG_LAZY_READONLY_INSTANCE_PROPERTY(message, getMessage); JSG_LAZY_READONLY_INSTANCE_PROPERTY(name, getName); JSG_LAZY_READONLY_INSTANCE_PROPERTY(stack, getStack); } void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("name", name); tracker.trackField("message", message); } private: double timestamp; kj::String name; kj::String message; kj::Maybe stack; }; class TraceMetrics final: public jsg::Object { public: explicit TraceMetrics(uint cpuTime, uint wallTime); uint getCPUTime() { return cpuTime; }; uint getWallTime() { return wallTime; }; JSG_RESOURCE_TYPE(TraceMetrics) { JSG_READONLY_INSTANCE_PROPERTY(cpuTime, getCPUTime); JSG_READONLY_INSTANCE_PROPERTY(wallTime, getWallTime); JSG_TS_ROOT(); } private: uint cpuTime; uint wallTime; }; class UnsafeTraceMetrics final: public jsg::Object { public: jsg::Ref fromTrace(jsg::Lock& js, jsg::Ref item); JSG_RESOURCE_TYPE(UnsafeTraceMetrics) { JSG_METHOD(fromTrace); JSG_TS_ROOT(); } }; class TraceCustomEvent final: public WorkerInterface::CustomEvent { public: TraceCustomEvent(uint16_t typeId, kj::Array> traces) : typeId(typeId), traces(kj::mv(traces)) {} kj::Promise run(kj::Own incomingRequest, kj::Maybe entrypointName, kj::Maybe versionInfo, Frankenvalue props, kj::TaskSet& waitUntilTasks, bool isDynamicDispatch) override; kj::Promise sendRpc(capnp::HttpOverCapnpFactory& httpOverCapnpFactory, capnp::ByteStreamFactory& byteStreamFactory, rpc::EventDispatcher::Client dispatcher) override; uint16_t getType() override { return typeId; } tracing::EventInfo getEventInfo() const override; kj::Promise notSupported() override { KJ_UNIMPLEMENTED("trace event not supported"); } static constexpr uint16_t TYPE = 2; private: uint16_t typeId; kj::Array> traces; }; #define EW_TRACE_ISOLATE_TYPES \ api::TracePreviewInfo, api::ScriptVersion, api::TailEvent, api::TraceItem, \ api::TraceItem::AlarmEventInfo, api::TraceItem::ConnectEventInfo, \ api::TraceItem::CustomEventInfo, api::TraceItem::ScheduledEventInfo, \ api::TraceItem::QueueEventInfo, api::TraceItem::EmailEventInfo, \ api::TraceItem::TailEventInfo, api::TraceItem::TailEventInfo::TailItem, \ api::TraceItem::FetchEventInfo, api::TraceItem::FetchEventInfo::Request, \ api::TraceItem::FetchEventInfo::Response, api::TraceItem::JsRpcEventInfo, \ api::TraceItem::HibernatableWebSocketEventInfo, \ api::TraceItem::HibernatableWebSocketEventInfo::Message, \ api::TraceItem::HibernatableWebSocketEventInfo::Close, \ api::TraceItem::HibernatableWebSocketEventInfo::Error, api::TraceLog, api::TraceException, \ api::TraceDiagnosticChannelEvent, api::TraceMetrics, api::UnsafeTraceMetrics // The list of trace.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE } // namespace workerd::api