Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,30 @@ export type {
SamplingResult,
} from "npm:@opentelemetry/sdk-trace-base@1.25.1";
export { NodeTracerProvider } from "npm:@opentelemetry/sdk-trace-node@1.25.1";
// Stable semantic conventions (OTel semconv) — use these instead of hardcoded
// attribute/metric name strings.
export {
SemanticResourceAttributes,
} from "npm:@opentelemetry/semantic-conventions@1.25.1";
ATTR_ERROR_TYPE,
ATTR_HTTP_REQUEST_METHOD,
ATTR_HTTP_RESPONSE_STATUS_CODE,
ATTR_HTTP_ROUTE,
ATTR_SERVER_ADDRESS,
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
ATTR_URL_PATH,
ATTR_URL_QUERY,
ATTR_URL_SCHEME,
ATTR_USER_AGENT_ORIGINAL,
METRIC_HTTP_SERVER_REQUEST_DURATION,
} from "npm:@opentelemetry/semantic-conventions@1.37.0";
// Incubating (not yet stable) semantic conventions.
export {
ATTR_CLOUD_PROVIDER,
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
ATTR_CLOUD_REGION,
ATTR_DEPLOYMENT_ENVIRONMENT_NAME,
ATTR_HTTP_REQUEST_BODY_SIZE,
ATTR_SERVICE_INSTANCE_ID,
} from "npm:@opentelemetry/semantic-conventions@1.37.0/incubating";

export {
ExplicitBucketHistogramAggregation,
Expand Down
23 changes: 15 additions & 8 deletions observability/http.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { ValueType } from "../deps.ts";
import {
ATTR_HTTP_REQUEST_METHOD,
ATTR_HTTP_RESPONSE_STATUS_CODE,
ATTR_HTTP_ROUTE,
METRIC_HTTP_SERVER_REQUEST_DURATION,
ValueType,
} from "../deps.ts";
import { meter } from "./otel/metrics.ts";

const httpDuration = meter.createHistogram("http_request_duration", {
description: "http request duration",
unit: "ms",
// OTel semconv: name `http.server.request.duration`, unit seconds.
const httpDuration = meter.createHistogram(METRIC_HTTP_SERVER_REQUEST_DURATION, {
description: "Duration of HTTP server requests.",
unit: "s",
valueType: ValueType.DOUBLE,
});
/**
Expand All @@ -12,10 +19,10 @@ const httpDuration = meter.createHistogram("http_request_duration", {
export const startObserve = () => {
const start = performance.now();
return (method: string, path: string, status: number) => {
httpDuration.record(Math.round(performance.now() - start), {
"http.method": method,
"http.route": path,
"http.response.status": `${status}`,
httpDuration.record((performance.now() - start) / 1000, {
[ATTR_HTTP_REQUEST_METHOD]: method,
[ATTR_HTTP_ROUTE]: path,
[ATTR_HTTP_RESPONSE_STATUS_CODE]: status,
});
};
};
30 changes: 19 additions & 11 deletions observability/observe.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { isWrappedError } from "../blocks/loader.ts";
import { ValueType } from "../deps.ts";
import { meter, OTEL_ENABLE_EXTRA_METRICS } from "./otel/metrics.ts";
import {
ATTR_DECO_OPERATION_ERROR,
ATTR_DECO_OPERATION_NAME,
METRIC_DECO_BLOCK_OPERATION_DURATION,
} from "./otel/conventions.ts";

const operationDuration = meter.createHistogram("block_op_duration", {
description: "operation duration",
unit: "ms",
valueType: ValueType.DOUBLE,
});
const operationDuration = meter.createHistogram(
METRIC_DECO_BLOCK_OPERATION_DURATION,
{
description: "Duration of deco block operations.",
unit: "s",
valueType: ValueType.DOUBLE,
},
);

/**
* Observe function durations based on the provided labels
Expand All @@ -16,21 +24,21 @@ export const observe = async <T>(
f: () => Promise<T>,
): Promise<T> => {
const start = performance.now();
let isError = "false";
let isError = false;
try {
const result = await f();
if (isWrappedError(result)) {
isError = "true";
isError = true;
}
return result;
} catch (error) {
isError = "true";
isError = true;
throw error;
} finally {
if (OTEL_ENABLE_EXTRA_METRICS) {
operationDuration.record(performance.now() - start, {
"operation.name": op,
"operation.is_error": isError,
operationDuration.record((performance.now() - start) / 1000, {
[ATTR_DECO_OPERATION_NAME]: op,
[ATTR_DECO_OPERATION_ERROR]: isError,
});
}
}
Expand Down
25 changes: 12 additions & 13 deletions observability/otel/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { Logger } from "@std/log/logger";
import { Context, context } from "../../deco.ts";
import denoJSON from "../../deno.json" with { type: "json" };
import {
ATTR_CLOUD_PROVIDER,
ATTR_CLOUD_REGION,
ATTR_DEPLOYMENT_ENVIRONMENT_NAME,
ATTR_SERVICE_INSTANCE_ID,
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
BatchSpanProcessor,
FetchInstrumentation,
NodeTracerProvider,
Expand All @@ -11,7 +17,6 @@ import {
ParentBasedSampler,
registerInstrumentations,
Resource,
SemanticResourceAttributes,
} from "../../deps.ts";
import { DenoRuntimeInstrumentation } from "./instrumentation/deno-runtime.ts";
import { DebugSampler } from "./samplers/debug.ts";
Expand All @@ -34,20 +39,14 @@ const apps_ver = tryGetVersionOf("apps/") ??

export const resource = Resource.default().merge(
new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: Deno.env.get(ENV_SITE_NAME) ??
"deco",
[SemanticResourceAttributes.SERVICE_VERSION]:
Context.active().deploymentId ??
Deno.hostname(),
[SemanticResourceAttributes.SERVICE_INSTANCE_ID]: crypto.randomUUID(),
[SemanticResourceAttributes.CLOUD_PROVIDER]: context.platform,
[ATTR_SERVICE_NAME]: Deno.env.get(ENV_SITE_NAME) ?? "deco",
[ATTR_SERVICE_VERSION]: Context.active().deploymentId ?? Deno.hostname(),
[ATTR_SERVICE_INSTANCE_ID]: crypto.randomUUID(),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
[ATTR_CLOUD_PROVIDER]: context.platform,
"deco.runtime.version": denoJSON.version,
"deco.apps.version": apps_ver,
[SemanticResourceAttributes.CLOUD_REGION]: Deno.env.get("DENO_REGION") ??
"unknown",
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: Deno.env.get(
"DECO_ENV_NAME",
)
[ATTR_CLOUD_REGION]: Deno.env.get("DENO_REGION") ?? "unknown",
[ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: Deno.env.get("DECO_ENV_NAME")
? `env-${Deno.env.get("DECO_ENV_NAME")}`
: "production",
}),
Expand Down
16 changes: 16 additions & 0 deletions observability/otel/conventions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// deco-proprietary telemetry conventions, for signals that have no OTel
// semantic-conventions equivalent. Standard signals (HTTP, URL, service,
// cloud, gen_ai, ...) MUST use the official @opentelemetry/semantic-conventions
// constants re-exported from deps.ts — do not hardcode those names here.

// Metrics
export const METRIC_DECO_BLOCK_OPERATION_DURATION =
"deco.block.operation.duration";
export const METRIC_DECO_CACHE_HITS = "deco.cache.hits";

// Attributes
export const ATTR_DECO_OPERATION_NAME = "deco.operation.name";
export const ATTR_DECO_OPERATION_ERROR = "deco.operation.error";
export const ATTR_DECO_CACHE_RESULT = "deco.cache.result";
export const ATTR_DECO_CACHE_ENGINE = "deco.cache.engine";
export const ATTR_DECO_CACHE_STATUS = "deco.cache.status";
20 changes: 18 additions & 2 deletions observability/otel/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,25 @@ const headersStringToObject = (headersString: string | undefined | null) => {
return Object.fromEntries(splitByComma);
};

// Add views with different boundaries for each unit.
// Add views with different boundaries for each unit. Now that durations are
// recorded in seconds (OTel semconv), the `s` buckets must cover both
// sub-second HTTP latencies and multi-second gen_ai/block operations.
const msBoundaries = [10, 100, 500, 1000, 5000, 10000, 15000];
const sBoundaries = [1, 5, 10, 50];
const sBoundaries = [
0.005,
0.01,
0.025,
0.05,
0.1,
0.25,
0.5,
1,
2.5,
5,
10,
30,
60,
];

type IMeter = ReturnType<MeterProvider["getMeter"]>;
const meterProvider: MeterProvider = new MeterProvider({
Expand Down
16 changes: 11 additions & 5 deletions runtime/caches/common.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { type Exception, ValueType } from "../../deps.ts";
import { tracer } from "../../observability/otel/config.ts";
import { meter } from "../../observability/otel/metrics.ts";
import {
ATTR_DECO_CACHE_ENGINE,
ATTR_DECO_CACHE_RESULT,
ATTR_DECO_CACHE_STATUS,
METRIC_DECO_CACHE_HITS,
} from "../../observability/otel/conventions.ts";
import { inFuture } from "./utils.ts";

export interface CacheMetrics {
engine: string;
total: number;
hits: number;
}
const cacheHit = meter.createCounter("cache_hit", {
const cacheHit = meter.createCounter(METRIC_DECO_CACHE_HITS, {
unit: "1",
valueType: ValueType.DOUBLE,
});
Expand Down Expand Up @@ -38,17 +44,17 @@ export const withInstrumentation = (
put: cacheImpl.put.bind(cacheImpl),
match: async (req, opts) => {
const span = tracer.startSpan("cache-match", {
attributes: { engine },
attributes: { [ATTR_DECO_CACHE_ENGINE]: engine },
});
try {
const isMatch = await cacheImpl.match(req, opts);
//there is an edge case where there is no expires header, but technically our loader always sets it
const result = getCacheStatus(isMatch);

span.setAttribute("cache_status", result);
span.setAttribute(ATTR_DECO_CACHE_STATUS, result);
cacheHit.add(1, {
result,
engine,
[ATTR_DECO_CACHE_RESULT]: result,
[ATTR_DECO_CACHE_ENGINE]: engine,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
return isMatch;
} catch (err) {
Expand Down
Loading