opentelemetry-js
opentelemetry-js copied to clipboard
SpanShim Log Removes Object (NonPrimitive) Properties
What happened?
Steps to Reproduce
https://github.com/open-telemetry/opentelemetry-js/tree/a8047ba9cdda65292778614ec9a1f8a2045faa9f/packages/opentelemetry-shim-opentracing
I call span.log({ event: 'ss', payload: { body: '...' }, strField: 'field' });
Expected Result
See payload as a json in Jaeger UI
Actual Result
The field was removed from log entry
Additional Details
OpenTelemetry Setup Code
const tracer = new TracerShim(
opentelemetry.api.trace.getTracer(packageJSON.name, packageJSON.version)
);
const span = tracer.startSpan('TEST');
const payload = {
doenstGoToLog: false,
};
span.log({ event: 'ss', payload, ok: true});
span.finish(); // Then only 'event' and 'ok' appears in span log
package.json
{
"@opentelemetry/api": "1.1.0",
"@opentelemetry/api-metrics": "0.29.0",
"@opentelemetry/core": "1.2.0",
"@opentelemetry/exporter-metrics-otlp-http": "0.29.0",
"@opentelemetry/exporter-prometheus": "^0.29.0",
"@opentelemetry/exporter-trace-otlp-http": "0.29.0",
"@opentelemetry/propagator-b3": "1.3.1",
"@opentelemetry/propagator-jaeger": "1.3.1",
"@opentelemetry/resources": "1.2.0",
"@opentelemetry/sdk-metrics-base": "^0.29.0",
"@opentelemetry/sdk-node": "0.29.0",
"@opentelemetry/semantic-conventions": "1.3.0"
}
Relevant log output
No response
For now I use this workaround (I'll create a PR with more clear ts code if u guys agree)
const isPrimitiveAttributeValue = (val) =>
['number', 'string', 'boolean'].includes(typeof val);
const serializeRecursive = (o, isRootCall = true) => {
if (isPrimitiveAttributeValue(o)) return o;
if (Array.isArray(o)) {
return isRootCall
? o.map((x) => serializeRecursive(x, false))
: JSON.stringify(o);
}
if (typeof o === 'object') return JSON.stringify(o);
return null;
};
const serializeSpanAttributeValue = (o) => serializeRecursive(o);
const serializeSpanAttribute = (o) =>
Object.entries(o).reduce((prev, [k, v]) => {
prev[k] = serializeSpanAttributeValue(v);
return prev;
}, {});
class ExtendedSpanShim extends SpanShim {
log(obj, ts) {
super.log(serializeSpanAttribute(obj), ts);
}
}
class ExtendedTracerShim extends TracerShim {
startSpan(name, options) {
const span = super.startSpan(name, options);
return new ExtendedSpanShim(
this,
span.getSpan(),
span.context().getBaggage()
);
}
}
const tracer = new ExtendedTracerShim(
opentelemetry.api.trace.getTracer(packageJSON.name, packageJSON.version)
);
// ...
For now I use this workaround (I'll create a PR with more clear ts code if u guys agree)
const isPrimitiveAttributeValue = (val) => ['number', 'string', 'boolean'].includes(typeof val); const serializeRecursive = (o, isRootCall = true) => { if (isPrimitiveAttributeValue(o)) return o; if (Array.isArray(o)) { return isRootCall ? o.map((x) => serializeRecursive(x, false)) : JSON.stringify(o); } if (typeof o === 'object') return JSON.stringify(o); return null; }; const serializeSpanAttributeValue = (o) => serializeRecursive(o); const serializeSpanAttribute = (o) => Object.entries(o).reduce((prev, [k, v]) => { prev[k] = serializeSpanAttributeValue(v); return prev; }, {}); class ExtendedSpanShim extends SpanShim { log(obj, ts) { super.log(serializeSpanAttribute(obj), ts); } } class ExtendedTracerShim extends TracerShim { startSpan(name, options) { const span = super.startSpan(name, options); return new ExtendedSpanShim( this, span.getSpan(), span.context().getBaggage() ); } } const tracer = new ExtendedTracerShim( opentelemetry.api.trace.getTracer(packageJSON.name, packageJSON.version) ); // ...
Thanks for the workaround example it helps to explain the situation that is happening too. I'm curious why you would have the recursive serialize and not just call JSON.stringify at the start? Can you give an example expected output format?
For now I use this workaround (I'll create a PR with more clear ts code if u guys agree)
const isPrimitiveAttributeValue = (val) => ['number', 'string', 'boolean'].includes(typeof val); const serializeRecursive = (o, isRootCall = true) => { if (isPrimitiveAttributeValue(o)) return o; if (Array.isArray(o)) { return isRootCall ? o.map((x) => serializeRecursive(x, false)) : JSON.stringify(o); } if (typeof o === 'object') return JSON.stringify(o); return null; }; const serializeSpanAttributeValue = (o) => serializeRecursive(o); const serializeSpanAttribute = (o) => Object.entries(o).reduce((prev, [k, v]) => { prev[k] = serializeSpanAttributeValue(v); return prev; }, {}); class ExtendedSpanShim extends SpanShim { log(obj, ts) { super.log(serializeSpanAttribute(obj), ts); } } class ExtendedTracerShim extends TracerShim { startSpan(name, options) { const span = super.startSpan(name, options); return new ExtendedSpanShim( this, span.getSpan(), span.context().getBaggage() ); } } const tracer = new ExtendedTracerShim( opentelemetry.api.trace.getTracer(packageJSON.name, packageJSON.version) ); // ...Thanks for the workaround example it helps to explain the situation that is happening too. I'm curious why you would have the recursive serialize and not just call
JSON.stringifyat the start? Can you give an example expected output format?
I use recursive serialization because in /shim.ts there are some conditions that made me think it's possible to use arrays in log fields, that's why I added extra serialization
But I'll add just JSON.stringify in my pr. Do u agree with it?
@dyladan #3236