Skip to content

Commit 5e62859

Browse files
authored
[EXPORTER] Elastic Search exporter follow ECS guidelines (open-telemetry#3107)
* [EXPORTERS]: elastic search log message within `message` key instead of `body` According to ECS logging reference https://www.elastic.co/guide/en/ecs/8.11/ecs-base.html#field-message Refs open-telemetry#3091 * [EXPORTERS]: elastic search set severity within `log.level` key instead of `severity` According to ECS logging reference https://www.elastic.co/guide/en/ecs/8.11/ecs-log.html#field-log-level Refs open-telemetry#3091 * [EXPORTERS]: elastic search set timestamp within `@timestamp` instead of `timestamp` Also changes the format to be a Date string. According to ECS logging reference https://www.elastic.co/guide/en/ecs/8.11/ecs-base.html#field-timestamp Refs open-telemetry#3091 * [EXPORTERS]: elastic search set instrumentation scope within `log.logger` instead of `name` According to ECS logging reference https://www.elastic.co/guide/en/ecs/8.11/ecs-log.html#field-log-logger Refs open-telemetry#3091 * EXPORTERS]: elastic search recorable constructor sets `ecs.version` field to 8.11.0 According to ECS guidelines this field is mandatory https://www.elastic.co/guide/en/ecs/8.11/ecs-guidelines.html Refs open-telemetry#3091 * [EXPORTERS]: elastic search put attributes in json root instead of under `attributes` This allows user to set other fields that are part of the [ECS log documentation](https://www.elastic.co/guide/en/ecs/8.11/ecs-log.html). For instance, it allows to have an attribute with key `log.file`, that will, thanks to `nlohmann::json`, appear as : ``` { "log": { "file": "xxx" } } ``` Closes open-telemetry#3091
1 parent 2c912d5 commit 5e62859

File tree

2 files changed

+46
-6
lines changed

2 files changed

+46
-6
lines changed

exporters/elasticsearch/include/opentelemetry/exporters/elasticsearch/es_log_recordable.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class ElasticSearchRecordable final : public sdk::logs::Recordable
4242
void WriteValue(const opentelemetry::common::AttributeValue &value, const std::string &name);
4343

4444
public:
45+
ElasticSearchRecordable() noexcept;
46+
4547
/**
4648
* Returns a JSON object contain the log information
4749
*/

exporters/elasticsearch/src/es_log_recordable.cc

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ void ElasticSearchRecordable::WriteValue(const opentelemetry::common::AttributeV
197197
}
198198
}
199199

200+
ElasticSearchRecordable::ElasticSearchRecordable() noexcept : sdk::logs::Recordable()
201+
{
202+
json_["ecs"]["version"] = "8.11.0";
203+
}
204+
200205
nlohmann::json ElasticSearchRecordable::GetJSON() noexcept
201206
{
202207
return json_;
@@ -205,7 +210,38 @@ nlohmann::json ElasticSearchRecordable::GetJSON() noexcept
205210
void ElasticSearchRecordable::SetTimestamp(
206211
opentelemetry::common::SystemTimestamp timestamp) noexcept
207212
{
208-
json_["timestamp"] = timestamp.time_since_epoch().count();
213+
const std::chrono::system_clock::time_point timePoint{timestamp};
214+
215+
// If built with with at least cpp 20 then use std::format
216+
// Otherwise use the old style to format the timestamp in UTC
217+
#if __cplusplus >= 202002L
218+
const std::string dateStr = std::format("{:%FT%T%Ez}", timePoint);
219+
#else
220+
const static int dateToSecondsSize = 19;
221+
const static int millisecondsSize = 8;
222+
const static int timeZoneSize = 1;
223+
const static int dateSize = dateToSecondsSize + millisecondsSize + timeZoneSize;
224+
225+
std::time_t time = std::chrono::system_clock::to_time_t(timePoint);
226+
std::tm tm = *std::gmtime(&time);
227+
228+
char bufferDate[dateSize]; // example: 2024-10-18T07:26:00.123456Z
229+
std::strftime(bufferDate, sizeof(bufferDate), "%Y-%m-%dT%H:%M:%S", &tm);
230+
auto microseconds =
231+
std::chrono::duration_cast<std::chrono::microseconds>(timePoint.time_since_epoch()) %
232+
std::chrono::seconds(1);
233+
234+
char bufferMilliseconds[millisecondsSize];
235+
std::snprintf(bufferMilliseconds, sizeof(bufferMilliseconds), ".%06ld",
236+
static_cast<long>(microseconds.count()));
237+
238+
std::strcat(bufferDate, bufferMilliseconds);
239+
std::strcat(bufferDate, "Z");
240+
241+
const std::string dateStr(bufferDate);
242+
#endif
243+
244+
json_["@timestamp"] = dateStr;
209245
}
210246

211247
void ElasticSearchRecordable::SetObservedTimestamp(
@@ -216,23 +252,25 @@ void ElasticSearchRecordable::SetObservedTimestamp(
216252

217253
void ElasticSearchRecordable::SetSeverity(opentelemetry::logs::Severity severity) noexcept
218254
{
255+
auto &severityField = json_["log"]["level"];
256+
219257
// Convert the severity enum to a string
220258
std::uint32_t severity_index = static_cast<std::uint32_t>(severity);
221259
if (severity_index >= std::extent<decltype(opentelemetry::logs::SeverityNumToText)>::value)
222260
{
223261
std::stringstream sout;
224262
sout << "Invalid severity(" << severity_index << ")";
225-
json_["severity"] = sout.str();
263+
severityField = sout.str();
226264
}
227265
else
228266
{
229-
json_["severity"] = opentelemetry::logs::SeverityNumToText[severity_index];
267+
severityField = opentelemetry::logs::SeverityNumToText[severity_index];
230268
}
231269
}
232270

233271
void ElasticSearchRecordable::SetBody(const opentelemetry::common::AttributeValue &message) noexcept
234272
{
235-
WriteValue(message, "body");
273+
WriteValue(message, "message");
236274
}
237275

238276
void ElasticSearchRecordable::SetTraceId(const opentelemetry::trace::TraceId &trace_id) noexcept
@@ -275,7 +313,7 @@ void ElasticSearchRecordable::SetAttribute(
275313
nostd::string_view key,
276314
const opentelemetry::common::AttributeValue &value) noexcept
277315
{
278-
WriteKeyValue(key, value, "attributes");
316+
WriteValue(value, key.data());
279317
}
280318

281319
void ElasticSearchRecordable::SetResource(
@@ -291,7 +329,7 @@ void ElasticSearchRecordable::SetInstrumentationScope(
291329
const opentelemetry::sdk::instrumentationscope::InstrumentationScope
292330
&instrumentation_scope) noexcept
293331
{
294-
json_["name"] = instrumentation_scope.GetName();
332+
json_["log"]["logger"] = instrumentation_scope.GetName();
295333
}
296334

297335
} // namespace logs

0 commit comments

Comments
 (0)