Skip to content

Instantly share code, notes, and snippets.

@afonsomatos
Created September 15, 2025 08:19
Show Gist options
  • Save afonsomatos/1fb4b4d4976d8d05256f7da4bd30ed36 to your computer and use it in GitHub Desktop.
Save afonsomatos/1fb4b4d4976d8d05256f7da4bd30ed36 to your computer and use it in GitHub Desktop.
Datadog Effect Logger
import {
Array,
Cause,
Context,
Effect,
Either,
FiberRef,
FiberRefs,
flow,
HashMap,
HashSet,
Inspectable,
Layer,
Logger,
Option,
Struct,
Tracer
} from "effect";
/**
* Format the traceId and spanId to be compatible with Datadog.
*
* Converts the OTEL traceId (128-bit uint as 32-hex-char string) and spanId (64-bit uint as 16-hex-char string)
* to the Datadog traceId and spanId (64-bit uint) format.
*/
const withDatadogFormat = (span: Tracer.AnySpan): Tracer.AnySpan => {
const { spanId, traceId } = span;
const traceIdEnd = traceId.slice(traceId.length / 2);
return Struct.evolve(span, {
traceId: () => BigInt(`0x${traceIdEnd}`).toString(),
spanId: () => BigInt(`0x${spanId}`).toString()
});
};
/**
* `Effect.fn` adds a virtual span, so we have to grab it from the outside.
*/
const filterSpan = (span: Option.Option<Tracer.AnySpan>): Option.Option<Tracer.AnySpan> => {
if (span._tag === "Some") {
if (span.value._tag === "Span" && span.value.name === "<anonymous>") {
return filterSpan(span.value.parent);
} else {
return span;
}
}
return Option.none();
};
/**
* Add identifiers from the current Span to the log annotations.
*/
const withDatadogSpanAnnotations = <Message, Output>(
self: Logger.Logger<Message, Output>
): Logger.Logger<Message, Output> =>
Logger.mapInputOptions(self, (options: Logger.Logger.Options<Message>) => {
const span = filterSpan(
Context.getOption(FiberRefs.getOrDefault(options.context, FiberRef.currentContext), Tracer.ParentSpan)
);
if (span._tag === "None") {
return options;
}
const { spanId, traceId } = withDatadogFormat(span.value);
return Struct.evolve(options, {
annotations: flow(
HashMap.set("dd.trace_id", traceId as unknown),
HashMap.set("dd.span_id", spanId as unknown)
)
});
});
/**
* A logger compatible with Datadog format. Objects are used for custom attributes.
*/
const DatadogLogger = Logger.make(({ logLevel, annotations, cause, message, date }) => {
const [messages, attributes] = Array.partitionMap(Array.ensure(message), (message) => {
if (Array.isArray(message)) {
return Either.left(message.join(" "));
} else if (typeof message === "object" && message !== null) {
return Either.right(message);
} else {
return Either.left(`${message}`);
}
});
const objAnnotations = Object.fromEntries(HashMap.entries(annotations));
return Inspectable.stringifyCircular({
level: logLevel.label,
timestamp: date.toISOString(),
// Usually it's just one
message: messages.join(" ").trim(),
// Some logs will be empty but have some errors, for example: Effect.tapErrorCause(Effect.logWarning)
cause: Cause.isEmpty(cause) ? undefined : Cause.pretty(cause, { renderErrorCause: true }),
// These include the datadog's tracing values
...objAnnotations,
// Assume that plain objects are to be custom attributes.
...Object.assign({}, ...attributes)
});
}).pipe(withDatadogSpanAnnotations, Logger.withConsoleLog);
export const LoggerLive = Layer.scopedDiscard(
Effect.locallyScoped(FiberRef.currentLoggers, HashSet.make(DatadogLogger))
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment