2025-08-17 CloudLogging でのOpenTelemetryのTrace情報伝搬の調査
CloudLoggingとCloudTraceを連携させるためにも動作を把握しておきたい。
CloudLoggingでログ送信する場合の基本的な方法
code: js
import {Logging} from '@google-cloud/logging-min'
const logging = new Logging();
// 標準出力にも表示するときはlogSyncを使う(defaultの出力先が標準出力)
// とりあえず Cloud Loggingに送るなら logを使う
// my-testは名前なので何でも良い
const log = logging.logSync('my-test');
const metadata = {
resource: {type: 'global'},
severity: 'INFO',
};
const entry = log.entry(metadata, 'message'); // messageがログの内容。構造化ログを送る場合はオブジェクトを渡す
このときのmetadataのデータ型は LogEntry という形式になる。
code: typescript
entry(metadata?: LogEntry, data?: string | {}): Entry;
LogEntryの構造はこのあたりが一番丁寧そう
traceを送る方法
LogEntryをみてみると関連するプロパティは以下
trace
spanId
traceSampled
ということで以下のようにするとCloud Loggingでトレースを認識してくれた。
code: javascript
const metadata = {
resource: {type: 'global'},
severity: 'INFO',
};
const entry = log.entry({
...metadata,
trace: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
spanId: 'aaaaaaaaaaaaaaaa',
trace_sampled: false,
},'message');
traceのprefix に projects/${project_id}/traces/ をつけるべきことが書かれているが別にこの形式になっていなくても送信や認識はできる。
OpenTelemetryのspan情報をおくる
上記はCloudLoggingの形式なのでOpenTelemetryでトレースを取っている場合はそれを変更する必要がある。
ログを送信する際にactiveなspanがあるはずなので、ここから情報を取り出して送れば良い
TraceId A valid trace identifier is a 16-byte array with at least one non-zero byte.
SpanId A valid span identifier is an 8-byte array with at least one non-zero byte.
TraceFlags contain details about the trace. Unlike TraceState values, TraceFlags are present in all traces. The current version of the specification supports two flags:
TraceIdをtrace,SpanIdをSpan,TraceFlagsをtrace_sampledに変換ができればよい。
TypeScriptのinterfaeを確認してみる
code: js
/**
* The ID of the trace that this span belongs to. It is worldwide unique
* with practically sufficient probability by being made as 16 randomly
* generated bytes, encoded as a 32 lowercase hex characters corresponding to
* 128 bits.
*/
traceId: string;
code: javascript
/**
* The ID of the Span. It is globally unique with practically sufficient
* probability by being made as 8 randomly generated bytes, encoded as a 16
* lowercase hex characters corresponding to 64 bits.
*/
spanId: string;
code: javascript
/**
* Trace flags to propagate.
*
* It is represented as 1 byte (bitmap). Bit to represent whether trace is
* sampled or not. When set, the least significant bit documents that the
* caller may have recorded trace data. A caller who does not record trace
* data out-of-band leaves this flag unset.
*
* see {@link TraceFlags} for valid flag values.
*/
traceFlags: number;
traceFlagsがbitmapなので微妙に面倒ではあるがCloudLoggingではここに実装がある。
code: javascript
const otelSpanContext = {
trace: spanContext?.traceId,
spanId: spanContext?.spanId,
traceSampled: (spanContext.traceFlags & FLAG_SAMPLED) !== 0,
};
これをtoCloudTraceContextに渡すことでtraceにprefixをつけている
code: typescript
function toCloudTraceContext(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
anyContext: any,
projectId: string
): CloudTraceContext {
const context: CloudTraceContext = {
trace: '',
};
if (anyContext?.trace) {
context.trace = projects/${projectId}/traces/${anyContext.trace};
}
if (anyContext?.spanId) {
context.spanId = anyContext.spanId;
}
if ('traceSampled' in anyContext) {
context.traceSampled = anyContext.traceSampled;
}
return context;
}
ということで、getContextFromOtelContext を使えば比較的簡単にOtelのトレースの情報を付与できそうである。
そして、このgetContextFromOtelContext をたどると Entry#toJSON と Entry#toStructedJSON での呼び出しが見つかる
toJSON(options: ToJsonOptions = {}, projectId = '') となっているので、OpenTelemetryのトレースがとれている状態で、第2引数を指定してみるとtrace情報が付与できていることが確認できる
code: javascript
console.log(entry.toJSON({}, 'my-project'))
また、実際にlogを書き込むところをみていると、 toStructedJSONを呼び出しており、projectIdはloggingオブジェクトからもってきている。
code: javascript
return entry.toStructuredJSON(
this.logging.projectId,
this.useMessageField_
);
ということは何もしなくても、トレースができていれば送れることになる。
以下のコードで確認
code: javascript
import { Logging } from '@google-cloud/logging-min'
import { NodeTracerProvider, SimpleSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node'
import { trace } from '@opentelemetry/api';
const tracerProvider = new NodeTracerProvider({
spanProcessors: [
new SimpleSpanProcessor(new ConsoleSpanExporter()),
]
})
tracerProvider.register();
const logging = new Logging({ projectId: 'my-dev'});
const log = logging.log('my-test');
const metadata = {
resource: {type: 'global'},
severity: 'INFO',
};
const tracer = trace.getTracer('sample-app');
await tracer.startActiveSpan('first', async (span) => {
const entry = log.entry(metadata,{
message: 'entry',
});
log.write(entry);
console.log(entry);
span.end()
})
await tracerProvider.shutdown()