From bf3e3833498d45865f85423764f0d98372fe5646 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Wed, 24 Jan 2024 11:03:31 -0800 Subject: [PATCH 01/25] [Geneva] Fix export name of Geneva metrics exporter (#366) --- exporters/geneva/CMakeLists.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/exporters/geneva/CMakeLists.txt b/exporters/geneva/CMakeLists.txt index 0612966bd..9a3c41dbd 100644 --- a/exporters/geneva/CMakeLists.txt +++ b/exporters/geneva/CMakeLists.txt @@ -59,8 +59,6 @@ else() PUBLIC opentelemetry_metrics opentelemetry_resources opentelemetry_common) endif() -set_target_properties(opentelemetry_exporter_geneva_metrics - PROPERTIES EXPORT_NAME metrics) set_target_version(opentelemetry_exporter_geneva_metrics) if(BUILD_TESTING) @@ -97,18 +95,20 @@ if(BUILD_TESTING) TEST_LIST geneva_metrics_exporter_test) endif() -install( - TARGETS opentelemetry_exporter_geneva_metrics - EXPORT "${PROJECT_NAME}-target" - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -install( - DIRECTORY include/opentelemetry/exporters/geneva - DESTINATION include/opentelemetry/exporters - FILES_MATCHING - PATTERN "*.h") +if(OPENTELEMETRY_INSTALL) + install( + TARGETS opentelemetry_exporter_geneva_metrics + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + install( + DIRECTORY include/opentelemetry/exporters/geneva + DESTINATION include/opentelemetry/exporters + FILES_MATCHING + PATTERN "*.h") +endif() if(BUILD_EXAMPLE) add_executable(example_metrics example/example_metrics.cc From d3ccb2a1ac0e539a147e2bcd8d1c9183e90aa98e Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Mon, 29 Jan 2024 20:04:33 -0800 Subject: [PATCH 02/25] [Geneva] Remove curl dependence for Geneva exporter on Linux (#370) --- exporters/geneva/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/exporters/geneva/CMakeLists.txt b/exporters/geneva/CMakeLists.txt index 9a3c41dbd..621539a17 100644 --- a/exporters/geneva/CMakeLists.txt +++ b/exporters/geneva/CMakeLists.txt @@ -15,11 +15,6 @@ if(OTELCPP_VERSIONED_LIBS AND NOT BUILD_SHARED_LIBS) message(FATAL_ERROR "OTELCPP_VERSIONED_LIBS=ON requires BUILD_SHARED_LIBS=ON") endif() - -if(NOT WIN32) - find_package(CURL REQUIRED) -endif() - add_definitions(-DHAVE_CONSOLE_LOG) if(MAIN_PROJECT) find_package(opentelemetry-cpp REQUIRED) From d50ae705cb0577bcda60171fc632b5612927b5ec Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Tue, 30 Jan 2024 10:49:47 -0800 Subject: [PATCH 03/25] [Geneva] install geneva-trace header files (#372) --- exporters/geneva-trace/CMakeLists.txt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/exporters/geneva-trace/CMakeLists.txt b/exporters/geneva-trace/CMakeLists.txt index 14f33c49b..bc095f4e1 100644 --- a/exporters/geneva-trace/CMakeLists.txt +++ b/exporters/geneva-trace/CMakeLists.txt @@ -1,11 +1,22 @@ cmake_minimum_required(VERSION 3.12) -option(WITH_EXAMPLES "Build examples" ON) +# MAIN_PROJECT CHECK +# determine if this project is built as a subproject (using add_subdirectory) or if this is the main project +set(MAIN_PROJECT OFF) +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + project(opentelemetry-geneva-trace-log-exporter) + set(MAIN_PROJECT ON) +endif() -project(opentelemetry-geneva-trace-log-exporter) - -include_directories(include) +if(MAIN_PROJECT) + option(WITH_EXAMPLES "Build examples" ON) +endif() if(WITH_EXAMPLES) + include_directories(include) add_subdirectory(example) +endif() + +if(OPENTELEMETRY_INSTALL) + install(DIRECTORY include/ DESTINATION include) endif() \ No newline at end of file From 1f3b11ed465447c99da8006c5220f59b59cf2c84 Mon Sep 17 00:00:00 2001 From: Anton Patsev <10828883+patsevanton@users.noreply.github.com> Date: Wed, 31 Jan 2024 00:53:13 +0600 Subject: [PATCH 04/25] Remove duplicate "the" (#371) --- instrumentation/otel-webserver-module/README.md | 4 ++-- .../scripts/gyp/r1919/pylib/gyp/MSVSSettings.py | 2 +- .../scripts/gyp/r1919/pylib/gyp/generator/make.py | 2 +- .../otel-webserver-module/src/util/SpanNamingUtils.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/instrumentation/otel-webserver-module/README.md b/instrumentation/otel-webserver-module/README.md index 8e17c46c1..42dd20b74 100644 --- a/instrumentation/otel-webserver-module/README.md +++ b/instrumentation/otel-webserver-module/README.md @@ -80,7 +80,7 @@ docker-compose --profile default up ``` Alternatively, replace the value of *profile* from **'default'** to **'centos7'** or **'ubuntu20.04'** to build in respective supported platforms. -This would start the container alongwith the the Opentelemetry Collector and Zipkin. You can check the traces on Zipkin dashboard by checking the port number of Zipkin using ```docker ps``` command. Multiple requests can be sent using the browser. +This would start the container alongwith the Opentelemetry Collector and Zipkin. You can check the traces on Zipkin dashboard by checking the port number of Zipkin using ```docker ps``` command. Multiple requests can be sent using the browser. #### Manual build and Installation @@ -198,7 +198,7 @@ docker-compose --profile centos_nginx up ``` Alternatively, replace the value of *centos_nginx* from **'centos_nginx'** to **'centos7_nginx'** or **'ubuntu20.04_nginx'** to build in respective supported platforms. -This would start the container alongwith the the Opentelemetry Collector and Zipkin. You can check the traces on Zipkin dashboard by checking the port number of Zipkin using ```docker ps``` command. Multiple requests can be sent using the browser. +This would start the container alongwith the Opentelemetry Collector and Zipkin. You can check the traces on Zipkin dashboard by checking the port number of Zipkin using ```docker ps``` command. Multiple requests can be sent using the browser. #### Manual build and Installation diff --git a/instrumentation/otel-webserver-module/scripts/gyp/r1919/pylib/gyp/MSVSSettings.py b/instrumentation/otel-webserver-module/scripts/gyp/r1919/pylib/gyp/MSVSSettings.py index b4e0a7890..71b613694 100644 --- a/instrumentation/otel-webserver-module/scripts/gyp/r1919/pylib/gyp/MSVSSettings.py +++ b/instrumentation/otel-webserver-module/scripts/gyp/r1919/pylib/gyp/MSVSSettings.py @@ -410,7 +410,7 @@ def FixVCMacroSlashes(s): def ConvertVCMacrosToMSBuild(s): - """Convert the the MSVS macros found in the string to the MSBuild equivalent. + """Convert the MSVS macros found in the string to the MSBuild equivalent. This list is probably not exhaustive. Add as needed. """ diff --git a/instrumentation/otel-webserver-module/scripts/gyp/r1919/pylib/gyp/generator/make.py b/instrumentation/otel-webserver-module/scripts/gyp/r1919/pylib/gyp/generator/make.py index 1de01b50c..919a5a7a0 100644 --- a/instrumentation/otel-webserver-module/scripts/gyp/r1919/pylib/gyp/generator/make.py +++ b/instrumentation/otel-webserver-module/scripts/gyp/r1919/pylib/gyp/generator/make.py @@ -19,7 +19,7 @@ # # Global settings and utility functions are currently stuffed in the # toplevel Makefile. It may make sense to generate some .mk files on -# the side to keep the the files readable. +# the side to keep the files readable. import os import re diff --git a/instrumentation/otel-webserver-module/src/util/SpanNamingUtils.cpp b/instrumentation/otel-webserver-module/src/util/SpanNamingUtils.cpp index 8d5aa9cb4..1ea424091 100755 --- a/instrumentation/otel-webserver-module/src/util/SpanNamingUtils.cpp +++ b/instrumentation/otel-webserver-module/src/util/SpanNamingUtils.cpp @@ -46,7 +46,7 @@ static const char URI_PARAMETER_DELIMITER = '.'; Parse a comma separated list of integers greater than 0 from the specified string into the specified boost::dynamic_bitset. -This function is used to parse the the url components list from the UI. +This function is used to parse the url components list from the UI. @param s The string to parse. Segment numbers in this string should be separated by URI_SUFFIX_KEY_SEPARATOR_STR. Any segment number that contains a character other than From 26e5ed48d81bb03fde52848ab394605dde0fb1a8 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Tue, 30 Jan 2024 14:22:56 -0800 Subject: [PATCH 05/25] [Geneva] add interface target for geneva-trace (#373) --- exporters/geneva-trace/CMakeLists.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/exporters/geneva-trace/CMakeLists.txt b/exporters/geneva-trace/CMakeLists.txt index bc095f4e1..3baec459e 100644 --- a/exporters/geneva-trace/CMakeLists.txt +++ b/exporters/geneva-trace/CMakeLists.txt @@ -12,6 +12,12 @@ if(MAIN_PROJECT) option(WITH_EXAMPLES "Build examples" ON) endif() +add_library(opentelemetry-cpp-geneva-trace-log-exporter INTERFACE) +target_include_directories( + opentelemetry-cpp-geneva-trace-log-exporter INTERFACE + $ + $) + if(WITH_EXAMPLES) include_directories(include) add_subdirectory(example) @@ -19,4 +25,15 @@ endif() if(OPENTELEMETRY_INSTALL) install(DIRECTORY include/ DESTINATION include) + + install( + TARGETS opentelemetry-cpp-geneva-trace-log-exporter + EXPORT "${PROJECT_NAME}-target") + + if(NOT MAIN_PROJECT) + install( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE "${PROJECT_NAME}::" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + endif() endif() \ No newline at end of file From e28f9cf5defe3d7792645efe2f406fcf7ef39808 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Tue, 6 Feb 2024 15:54:28 -0800 Subject: [PATCH 06/25] Update opentelemetry-cpp for geneva-trace exporter to latest (#374) --- exporters/geneva-trace/third_party/opentelemetry-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/geneva-trace/third_party/opentelemetry-cpp b/exporters/geneva-trace/third_party/opentelemetry-cpp index ebb45c6a3..d32f9609b 160000 --- a/exporters/geneva-trace/third_party/opentelemetry-cpp +++ b/exporters/geneva-trace/third_party/opentelemetry-cpp @@ -1 +1 @@ -Subproject commit ebb45c6a3b35e2893bc41ef1b7885138054af9bc +Subproject commit d32f9609b965dbb34030c1740ddb464d93cd3065 From fce77f674c12bab9fdb38326427e48481fb62993 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Wed, 7 Feb 2024 21:58:25 +0100 Subject: [PATCH 07/25] [ADMIN] Document existing maintainers / approvers / emeritus (#375) --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b7319c06..0f652099d 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,47 @@ can find their home here. ## Contributing -For information on how to contribute, consult [the contributing -guidelines](./CONTRIBUTING.md) +See [CONTRIBUTING.md](CONTRIBUTING.md) + +We meet weekly, and the time of the meeting alternates between Monday at 13:00 +PT and Wednesday at 9:00 PT. The meeting is subject to change depending on +contributors' availability. Check the [OpenTelemetry community +calendar](https://calendar.google.com/calendar/embed?src=google.com_b79e3e90j7bbsa2n2p5an5lf60%40group.calendar.google.com) +for specific dates and Zoom meeting links. + +Meeting notes are available as a public [Google +doc](https://docs.google.com/document/d/1i1E4-_y4uJ083lCutKGDhkpi3n4_e774SBLi9hPLocw/edit?usp=sharing). +For edit access, get in touch on +[Slack](https://cloud-native.slack.com/archives/C01N3AT62SJ). + +[Maintainers](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer) +([@open-telemetry/cpp-contrib-maintainers](https://github.com/orgs/open-telemetry/teams/cpp-contrib-maintainers)): + +* [Lalit Kumar Bhasin](https://github.com/lalitb), Microsoft + +[Approvers](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver) +([@open-telemetry/cpp-contrib-approvers](https://github.com/orgs/open-telemetry/teams/cpp-contrib-approvers)): + +* [DEBAJIT DAS](https://github.com/DebajitDas), Cisco +* [Ehsan Saei](https://github.com/esigo) +* [Johannes Tax](https://github.com/pyohannes), Grafana Labs +* [Josh Suereth](https://github.com/jsuereth), Google +* [Kumar Pratyush](https://github.com/kpratyus), Cisco +* [Marc Alff](https://github.com/marcalff), Oracle +* [Max Golovanov](https://github.com/maxgolov), Microsoft +* [Siim Kallas](https://github.com/seemk), Splunk +* [Tobias Stadler](https://github.com/tobiasstadler) +* [Tomasz Rojek](https://github.com/TomRoSystems) +* [Tom Tan](https://github.com/ThomsonTan), Microsoft + +[Emeritus +Maintainer/Approver/Triager](https://github.com/open-telemetry/community/blob/main/community-membership.md#emeritus-maintainerapprovertriager): + +* None + +### Thanks to all the people who have contributed + +[![contributors](https://contributors-img.web.app/image?repo=open-telemetry/opentelemetry-cpp-contrib)](https://github.com/open-telemetry/opentelemetry-cpp-contrib/graphs/contributors) ## Support From 5f452b67e6de1ee5d3417760f94dc5ff241602f8 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Wed, 7 Feb 2024 22:09:26 +0100 Subject: [PATCH 08/25] [ADMIN], Add Marc as maintainer. (#376) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f652099d..248a3570e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ For edit access, get in touch on ([@open-telemetry/cpp-contrib-maintainers](https://github.com/orgs/open-telemetry/teams/cpp-contrib-maintainers)): * [Lalit Kumar Bhasin](https://github.com/lalitb), Microsoft +* [Marc Alff](https://github.com/marcalff), Oracle [Approvers](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver) ([@open-telemetry/cpp-contrib-approvers](https://github.com/orgs/open-telemetry/teams/cpp-contrib-approvers)): @@ -32,7 +33,6 @@ For edit access, get in touch on * [Johannes Tax](https://github.com/pyohannes), Grafana Labs * [Josh Suereth](https://github.com/jsuereth), Google * [Kumar Pratyush](https://github.com/kpratyus), Cisco -* [Marc Alff](https://github.com/marcalff), Oracle * [Max Golovanov](https://github.com/maxgolov), Microsoft * [Siim Kallas](https://github.com/seemk), Splunk * [Tobias Stadler](https://github.com/tobiasstadler) From d9dc93bf7d72d2863ee82fc7d3208787a5f84f28 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Wed, 7 Feb 2024 16:42:26 -0800 Subject: [PATCH 09/25] [Geneva] add geneva header files for fluentd exporter (#377) --- .../geneva/geneva_exporter_options.h | 47 +++++++++++++++++ .../exporters/geneva/geneva_logger_exporter.h | 50 ++++++++++++++++++ .../exporters/geneva/geneva_tracer_exporter.h | 51 +++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_exporter_options.h create mode 100644 exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_logger_exporter.h create mode 100644 exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_tracer_exporter.h diff --git a/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_exporter_options.h b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_exporter_options.h new file mode 100644 index 000000000..45d6fc9f0 --- /dev/null +++ b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_exporter_options.h @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporters { + +namespace geneva { + +constexpr const char *kUnixDomainScheme = "unix://"; +constexpr const size_t kUnixDomainSchemeLength = 7; // length of "unix://" +struct GenevaExporterOptions { + + /** socker path for unix domain socket. Should start with unix:// + * Example unix:///tmp/.socket_geneva_exporter + */ + std::string socket_endpoint ; + + /* number of retries before failing */ + + size_t retry_count = 2; + + /** + * The maximum buffer/queue size. After the size is reached, spans/logs are + * dropped. + */ + size_t max_queue_size = 2048; // max buffer size dropping logs/spans + + /* The time interval between two consecutive exports. */ + std::chrono::milliseconds schedule_delay_millis = std::chrono::milliseconds(5000); + + /** + * The maximum batch size of every export. It must be smaller or + * equal to max_queue_size. + */ + size_t max_export_batch_size = 512; +}; + +} +} +OPENTELEMETRY_END_NAMESPACE + diff --git a/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_logger_exporter.h b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_logger_exporter.h new file mode 100644 index 000000000..d5a05db8c --- /dev/null +++ b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_logger_exporter.h @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define ENABLE_LOGS_PREVIEW +#include "geneva_exporter_options.h" +#include +#include +#include +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporters { + +namespace geneva { + +class GenevaLoggerExporter { + +public: + +static inline bool InitializeGenevaExporter( const GenevaExporterOptions options ) { + // Use only unix-domain IPC for agent connectivity + if (options.socket_endpoint.size() > kUnixDomainSchemeLength && options.socket_endpoint.substr(0, kUnixDomainSchemeLength) == std::string(kUnixDomainScheme)){ + opentelemetry::exporter::fluentd::common::FluentdExporterOptions fluentd_options; + fluentd_options.retry_count = options.retry_count; + fluentd_options.endpoint = options.socket_endpoint; + auto exporter = std::unique_ptr( + new opentelemetry::exporter::fluentd::logs::FluentdExporter(fluentd_options)); + auto processor = std::unique_ptr( + new opentelemetry::sdk::logs::BatchLogProcessor(std::move(exporter), options.max_queue_size, options.schedule_delay_millis, options.max_export_batch_size)); + auto provider = std::shared_ptr( + new opentelemetry::sdk::logs::LoggerProvider(std::move(processor))); + // Set the global logger provider + opentelemetry::logs::Provider::SetLoggerProvider(provider); + return true; + } else { +#if defined(__EXCEPTIONS) + throw new std::runtime_error("Invalid endpoint! Unix domain socket should have unix:// as url-scheme"); +#endif + return false; + } +} + +}; +} +} +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_tracer_exporter.h b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_tracer_exporter.h new file mode 100644 index 000000000..bbf1c684f --- /dev/null +++ b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_tracer_exporter.h @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define USE_UNIX_SOCKETS +#include "geneva_exporter_options.h" +#include +#include +#include +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE + +namespace exporters { + +namespace geneva { + +class GenevaTracerExporter { + +public: +static inline bool InitializeGenevaExporter( const GenevaExporterOptions options ) { + // Use only unix-domain IPC for agent connectivity + if (options.socket_endpoint.size() > kUnixDomainSchemeLength && options.socket_endpoint.substr(0, kUnixDomainSchemeLength) == std::string(kUnixDomainScheme)){ + opentelemetry::exporter::fluentd::common::FluentdExporterOptions fluentd_options; + opentelemetry::sdk::trace::BatchSpanProcessorOptions batch_processor_options; + fluentd_options.retry_count = options.retry_count; + fluentd_options.endpoint = options.socket_endpoint; + batch_processor_options.max_queue_size = options.max_queue_size; + batch_processor_options.schedule_delay_millis = options.schedule_delay_millis; + batch_processor_options.max_export_batch_size = options.max_export_batch_size; + auto exporter = std::unique_ptr( + new opentelemetry::exporter::fluentd::trace::FluentdExporter(fluentd_options)); + auto processor = std::unique_ptr( + new opentelemetry::sdk::trace::BatchSpanProcessor(std::move(exporter), batch_processor_options)); + auto provider = opentelemetry::nostd::shared_ptr( + new opentelemetry::sdk::trace::TracerProvider(std::move(processor))); + opentelemetry::trace::Provider::SetTracerProvider(provider); + return true; + } else { +#if defined(__EXCEPTIONS) + throw new std::runtime_error("Invalid endpoint! Unix domain socket should have unix:// as url-scheme"); +#endif + return false; + } +} +}; +} +} +OPENTELEMETRY_END_NAMESPACE From edeef1ba6b1b241b084822fba656593d6bb1937b Mon Sep 17 00:00:00 2001 From: Alex E <36134278+chusitoo@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:10:08 -0500 Subject: [PATCH 10/25] [Logs SDK] Log Appender for Boost.Log (#359) --- .clang-format | 61 +++ instrumentation/README.md | 1 + instrumentation/boost_log/CMakeLists.txt | 117 ++++ instrumentation/boost_log/README.md | 256 +++++++++ instrumentation/boost_log/example/main.cc | 86 +++ .../instrumentation/boost_log/sink.h | 101 ++++ ...entelemetry_boost_log_sink-config.cmake.in | 5 + instrumentation/boost_log/src/sink.cc | 145 +++++ instrumentation/boost_log/test/sink_test.cc | 503 ++++++++++++++++++ 9 files changed, 1275 insertions(+) create mode 100644 .clang-format create mode 100644 instrumentation/boost_log/CMakeLists.txt create mode 100644 instrumentation/boost_log/README.md create mode 100644 instrumentation/boost_log/example/main.cc create mode 100644 instrumentation/boost_log/include/opentelemetry/instrumentation/boost_log/sink.h create mode 100644 instrumentation/boost_log/opentelemetry_boost_log_sink-config.cmake.in create mode 100644 instrumentation/boost_log/src/sink.cc create mode 100644 instrumentation/boost_log/test/sink_test.cc diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..638d905c4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,61 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +# See Clang docs: http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium + +# Allow double brackets such as std::vector>. +Standard: Cpp11 + +# Indent 2 spaces at a time. +IndentWidth: 2 + +# Keep lines under 100 columns long. +ColumnLimit: 100 + +# Always break before braces +BreakBeforeBraces: Custom +BraceWrapping: +# TODO(lujc) wait for clang-format-9 support in Chromium tools +# AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + + # Keeps extern "C" blocks unindented. + AfterExternBlock: false + +# Indent case labels. +IndentCaseLabels: true + +# Right-align pointers and references +PointerAlignment: Right + +# ANGLE likes to align things as much as possible. +AlignOperands: true +AlignConsecutiveAssignments: true + +# Use 2 space negative offset for access modifiers +AccessModifierOffset: -2 + +# TODO(jmadill): Decide if we want this on. Doesn't have an "all or none" mode. +AllowShortCaseLabelsOnASingleLine: false + +# Useful for spacing out functions in classes +KeepEmptyLinesAtTheStartOfBlocks: true + +# Indent nested PP directives. +IndentPPDirectives: AfterHash + +# Include blocks style +IncludeBlocks: Preserve diff --git a/instrumentation/README.md b/instrumentation/README.md index 5a50c71f0..4eee5dc0a 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -4,6 +4,7 @@ In this directory you will find instrumentation libraries and modules. | Name | Description | |---|---| +| [boost-log](./boost_log) | Boost.Log OpenTelemetry sink backend | | [httpd](./httpd) | httpd (Apache) OpenTelemetry module | | [nginx](./nginx) | OpenTelemetry nginx module | | [otel-webserver-module](./otel-webserver-module) | The OTEL webserver module comprises of both Apache and Nginx instrumentation. | diff --git a/instrumentation/boost_log/CMakeLists.txt b/instrumentation/boost_log/CMakeLists.txt new file mode 100644 index 000000000..48de54d71 --- /dev/null +++ b/instrumentation/boost_log/CMakeLists.txt @@ -0,0 +1,117 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.12) + +set(this_target opentelemetry_boost_log_sink) + +project(${this_target}) + +find_package(opentelemetry-cpp REQUIRED) +find_package(Boost COMPONENTS log REQUIRED) + +add_library(${this_target} src/sink.cc) + +target_compile_options(${this_target} PUBLIC + -Wall -Wextra -Werror -Wpedantic -fPIC +) + +set_target_properties(${this_target} PROPERTIES EXPORT_NAME ${this_target}) + +target_include_directories(${this_target} PUBLIC + $ + $ + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} +) + +target_link_libraries(${this_target} PRIVATE + Boost::log +) + +if(OPENTELEMETRY_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + + install( + TARGETS ${this_target} + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + install( + DIRECTORY include/opentelemetry/instrumentation/boost_log + DESTINATION include/opentelemetry/instrumentation + FILES_MATCHING + PATTERN "*.h") + + configure_package_config_file( + "${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + NO_CHECK_REQUIRED_COMPONENTS_MACRO) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + + export( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE ${PROJECT_NAME}:: + FILE "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-target.cmake") + + install( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE ${PROJECT_NAME}:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +endif() # OPENTELEMETRY_INSTALL + +if(BUILD_TESTING) + set(testname sink_test) + + include(GoogleTest) + + add_executable(${testname} "test/${testname}.cc") + + target_include_directories(${testname} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} + ) + + target_link_libraries(${testname} PRIVATE + gmock + gtest + gtest_main + Boost::log + opentelemetry-cpp::ostream_log_record_exporter + ${this_target} + ) + + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX intrumentation.boost_log. + TEST_LIST ${testname} + ) +endif() # BUILD_TESTING + +if(WITH_EXAMPLES) + set(example_exe otel_sink_example) + add_executable(${example_exe} example/main.cc) + set_target_properties(${example_exe} PROPERTIES EXPORT_NAME ${example_exe}) + + target_include_directories(${example_exe} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} + ) + + target_link_libraries(${example_exe} PRIVATE + opentelemetry-cpp::logs + opentelemetry-cpp::trace + opentelemetry-cpp::ostream_log_record_exporter + opentelemetry-cpp::ostream_span_exporter + Boost::log + ${this_target} + ) +endif() # WITH_EXAMPLES diff --git a/instrumentation/boost_log/README.md b/instrumentation/boost_log/README.md new file mode 100644 index 000000000..5480d9187 --- /dev/null +++ b/instrumentation/boost_log/README.md @@ -0,0 +1,256 @@ +# Boost log OpenTelemetry sink backend + +## Features + +- Supports Boost.Log sink backend mechanism and allows for some customizations +- Supports OpenTelemetry SDK without any changes + +## Requirements + +- Current release tested only with Ubuntu 20.04.6 LTS +- OpenTelemetry >= v1.12.0 +- Boost.Log >= v1.73.0 + +### Usage + +Please see below for [manual build](#build). Otherwise please use one of the [released versions](https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases). + +### Configuration + +The OpenTelemetry sink can be configured in much the same way as any other Boost log sink backend. + +The simplest scenario is by creating a logger with the `OpenTelemetrySinkBackend`: + +```cpp +#include + +using opentelemetry::instrumentation::boost_log::OpenTelemetrySinkBackend; + +auto backend = boost::make_shared(); +auto sink = boost::make_shared>(backend); +boost::log::core::get()->add_sink(sink); +``` + +This will create a backend with the following assumptions about the attributes it collects, more precisely: + +| Keyword | Type | +|--------------|-----------------------------| +| Severity | int | +| TimeStamp | boost::posix_time::ptime | +| ThreadID | boost::log::aux::thread::id | +| FileName | std::string | +| FunctionName | std::string | +| LineNumber | int | + +If, however, one or more of these attributes have a different name or type, it is possible to communicate this to the backend via the `ValueMappers` struct. It contains a function for each attribute described above, with their signatures as follows: + +```cpp +opentelemetry::logs::Severity ToSeverity(const boost::log::record_view &); +bool ToTimestamp(const boost::log::record_view &record, std::chrono::system_clock::time_point &value); +bool ToThreadId(const boost::log::record_view &record, std::string &value); +bool ToFileName(const boost::log::record_view &record, std::string &value); +bool ToFuncName(const boost::log::record_view &record, std::string &value); +bool ToLineNumber(const boost::log::record_view &record, int &value); +``` + +With the exception of `ToSeverity`, which is always required to return some OpenTelemetry severity type, the requirements for the other methods are more relaxed, essentially making them optional. They take an additional *out* parameter **value** and return a boolean to indicate success or failure. + +As an example of usage, below is one way to create the OTel sink backend with a custom `ToSeverity` method that maps to an enum class `CustomSeverity` and reads the value from the attribute *"LogLevel"* instead of the default *"Severity"*: + +```cpp +enum class CustomSeverity +{ + kRed, kOrange, kYellow, kGreen, kBlue, kIndigo, kViolet +}; + +opentelemetry::instrumentation::boost_log::ValueMappers mappers; +mappers.ToSeverity = [](const boost::log::record_view &record) { + if (const auto &result = boost::log::extract(record["LogLevel"])) + { + switch (result.get()) + { + case CustomSeverity::kRed: + return opentelemetry::logs::Severity::kFatal; + case CustomSeverity::kOrange: + return opentelemetry::logs::Severity::kError; + case CustomSeverity::kYellow: + return opentelemetry::logs::Severity::kWarn; + case CustomSeverity::kGreen: + return opentelemetry::logs::Severity::kInfo; + case CustomSeverity::kBlue: + return opentelemetry::logs::Severity::kDebug; + case CustomSeverity::kIndigo: + return opentelemetry::logs::Severity::kTrace; + case CustomSeverity::kViolet: + [[fallthrough]]; + default: + return Severity::kInvalid; + } + } + + return opentelemetry::logs::Severity::kInvalid; +}; + +auto backend = boost::make_shared(mappers); +auto sink = boost::make_shared>(backend); +boost::log::core::get()->add_sink(sink); +``` + +Another, although somewhat impractical, example of setting a custom timestamp: + +```cpp +mappers.ToTimeStamp = [](const boost::log::record_view &, + std::chrono::system_clock::time_point &value) { + value = std::chrono::system_clock::now() - std::chrono::milliseconds(42); + return true; +}; +``` + +For more details, refer to the [examples](#examples) section. + +## Development + +### Requirements + +- C++14 +- CMake 3.x +- [OpenTelemetry-cpp](https://github.com/open-telemetry/opentelemetry-cpp) +- [Boost.Log](https://github.com/boostorg/log) +- vcpkg **_(optional)_** + +### Build +As a preparation step, both dependencies need to be built and available in the development environment. This can be a manual build, by following the instructions for the corresponding package, or one could opt to use a package management system such as _vcpkg_ or _conan_. + +Assuming the packages are available on the system, configure CMake as usual: + +```bash +mkdir build +cd build +cmake [path/to/opentelemetry-cpp-contrib]/instrumentation/boost_log -DBUILD_SHARED_LIBS=ON +make +``` + +Optionally, if the packages were provided via vcpkg, pass in to the _cmake_ command above the flag `-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake` where VCPKG_ROOT is where vcpkg was installed. + +Now, simply link the target source (i.e., main.cc in the example below) against _boost_log_ as well as _opentelemetry_boost_log_sink_: + +```bash +g++ main.cc -lboost_log -lopentelemetry_boost_log_sink -o main +``` + +### Installation ### + +When configuring the build, if the flag `-DOPENTELEMETRY_INSTALL=ON` is passed, CMake will ensure to set up the install scripts. Once the build succeeds, running `make install` will make the sink header(s) and library available under the usr include and lib directories, respectively. + +### Testing + +A small suite of unit tests is available under the `test` directory. It can be enabled by passing `-DBUILD_TESTING=ON` when configuring CMake, which will generate an executable called **sink_test**. + +### Examples + +An example executable is available to test the functionality of the sink. For ease of setup, it uses the _OStreamLogRecordExporter_ to display the contents of the intercepted Boost log message as an OTel _LogRecord_. It can be generated by adding `-DWITH_EXAMPLES=ON` when configuring CMake, which will ultimately produce the **otel_sink_example** executable. + +Running `./otel_sink_example` would produce an output similar to following excerpts. + +``` +{ + timestamp : 1704766376214174000 + observed_timestamp : 1704766376214298462 + severity_num : 0 + severity_text : INVALID + body : Test simplest message + resource : + service.name: unknown_service + telemetry.sdk.version: 1.12.0 + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + attributes : + thread.id: 0x00007f5d78f932c0 + event_id : 0 + event_name : + trace_id : 3b74715eaab1e0026feff669ee7f27a1 + span_id : a51c93eef9e7af66 + trace_flags : 01 + scope : + name : Boost.Log + version : 1.83.0 + schema_url : + attributes : +} +``` +The above is an example of calling BOOST_LOG with a simple logger that is not severity-aware and, as such, the severity is invalid. + +``` +{ + timestamp : 1704766376214570000 + observed_timestamp : 1704766376214576298 + severity_num : 9 + severity_text : INFO + body : Test message with severity + resource : + service.name: unknown_service + telemetry.sdk.version: 1.12.0 + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + attributes : + thread.id: 0x00007f5d78f932c0 + event_id : 0 + event_name : + trace_id : 3b74715eaab1e0026feff669ee7f27a1 + span_id : a51c93eef9e7af66 + trace_flags : 01 + scope : + name : Boost.Log + version : 1.83.0 + schema_url : + attributes : +} +``` +This example is the result of calling BOOST_LOG_SEV with a severity logger and trivial severity level. + +``` +{ + timestamp : 1704766376214627000 + observed_timestamp : 1704766376214643807 + severity_num : 5 + severity_text : DEBUG + body : Test message with source location + resource : + service.name: unknown_service + telemetry.sdk.version: 1.12.0 + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + attributes : + code.lineno: 90 + code.function: main + code.filepath: /otel-contrib/instrumentation/boost_log/example/main.cc + thread.id: 0x00007f5d78f932c0 + event_id : 0 + event_name : + trace_id : 3b74715eaab1e0026feff669ee7f27a1 + span_id : a51c93eef9e7af66 + trace_flags : 01 + scope : + name : Boost.Log + version : 1.83.0 + schema_url : + attributes : +} +``` + +The above calls the same macro as in the previous example, with the values added for the source location (line / function / file). + +The default behavior to log the source location is by adding attributes values to the BOOST_LOG* macro with the following keywords: + +```cpp +BOOST_LOG_SEV(logger, boost::log::trivial::debug) + << boost::log::add_value("FileName", __FILE__) + << boost::log::add_value("FunctionName", __FUNCTION__) + << boost::log::add_value("LineNumber", __LINE__) << "Test message with source location"; +``` + +If logging with a different keyword or type, a custom mapping method has to be provided when instantiating the backend sink, as shown in [configuration](#configuration) section. + +For all the examples, the common attributes were enabled with `boost::log::add_common_attributes();`. Otherwise, the timestamp would not be populated and thread ID would not be included at all in the exported log record. + +This example will also output a span with the same ID as the log record, to showcase how the two signals can be correlated via trace and/or span ID. diff --git a/instrumentation/boost_log/example/main.cc b/instrumentation/boost_log/example/main.cc new file mode 100644 index 000000000..723f603d8 --- /dev/null +++ b/instrumentation/boost_log/example/main.cc @@ -0,0 +1,86 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace context = opentelemetry::context; +namespace logs_exp = opentelemetry::exporter::logs; +namespace logs_api = opentelemetry::logs; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace trace_exp = opentelemetry::exporter::trace; +namespace trace_api = opentelemetry::trace; +namespace trace_sdk = opentelemetry::sdk::trace; + +int main(int /* argc */, char ** /* argv */) +{ + // Set up logger provider + { + using ProviderPtr = opentelemetry::nostd::shared_ptr; + auto exporter = logs_exp::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + auto provider = logs_sdk::LoggerProviderFactory::Create(std::move(processor)); + logs_api::Provider::SetLoggerProvider(ProviderPtr(provider.release())); + } + + // Set up tracer provider + { + using ProviderPtr = opentelemetry::nostd::shared_ptr; + auto exporter = trace_exp::OStreamSpanExporterFactory::Create(); + auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter)); + auto provider = trace_sdk::TracerProviderFactory::Create(std::move(processor)); + trace_api::Provider::SetTracerProvider(ProviderPtr(provider.release())); + } + + // Set up trace, span and context + auto tracer = trace_api::Provider::GetTracerProvider()->GetTracer("boost_library", + OPENTELEMETRY_SDK_VERSION); + auto span = tracer->StartSpan("boost log test span"); + auto ctx = context::RuntimeContext::GetCurrent(); + auto new_ctx = ctx.SetValue("active_span", span); + auto token = context::RuntimeContext::Attach(new_ctx); + + // Set up loggers + { + using boost::log::sinks::synchronous_sink; + using opentelemetry::instrumentation::boost_log::OpenTelemetrySinkBackend; + auto backend = boost::make_shared(); + auto sink = boost::make_shared>(backend); + boost::log::core::get()->add_sink(sink); + boost::log::add_common_attributes(); + } + + boost::log::sources::logger simple; + BOOST_LOG(simple) << "Test simplest message"; + + boost::log::sources::severity_logger logger; + BOOST_LOG_SEV(logger, boost::log::trivial::info) << "Test message with severity"; + + BOOST_LOG_SEV(logger, boost::log::trivial::debug) + << boost::log::add_value("FileName", __FILE__) + << boost::log::add_value("FunctionName", __FUNCTION__) + << boost::log::add_value("LineNumber", __LINE__) << "Test message with source location"; +} diff --git a/instrumentation/boost_log/include/opentelemetry/instrumentation/boost_log/sink.h b/instrumentation/boost_log/include/opentelemetry/instrumentation/boost_log/sink.h new file mode 100644 index 000000000..ce25aad05 --- /dev/null +++ b/instrumentation/boost_log/include/opentelemetry/instrumentation/boost_log/sink.h @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace opentelemetry +{ +namespace instrumentation +{ +namespace boost_log +{ + +using IntMapper = std::function; +using StringMapper = std::function; +using SeverityMapper = + std::function; +using TimeStampMapper = + std::function; + +using IntSetter = std::function; +using StringSetter = std::function; +using TimeStampSetter = std::function; + +struct ValueMappers +{ + SeverityMapper ToSeverity; + TimeStampMapper ToTimeStamp; + StringMapper ToThreadId; + StringMapper ToCodeFile; + StringMapper ToCodeFunc; + IntMapper ToCodeLine; +}; + +class OpenTelemetrySinkBackend + : public boost::log::sinks::basic_sink_backend +{ +public: + static const std::string &libraryVersion() + { + static const std::string kLibraryVersion = std::to_string(BOOST_VERSION / 100000) + "." + + std::to_string(BOOST_VERSION / 100 % 1000) + "." + + std::to_string(BOOST_VERSION % 100); + return kLibraryVersion; + } + + static inline opentelemetry::logs::Severity levelToSeverity(int level) noexcept + { + using namespace boost::log::trivial; + using opentelemetry::logs::Severity; + + switch (level) + { + case severity_level::fatal: + return Severity::kFatal; + case severity_level::error: + return Severity::kError; + case severity_level::warning: + return Severity::kWarn; + case severity_level::info: + return Severity::kInfo; + case severity_level::debug: + return Severity::kDebug; + case severity_level::trace: + return Severity::kTrace; + default: + return Severity::kInvalid; + } + } + + OpenTelemetrySinkBackend() noexcept : OpenTelemetrySinkBackend(ValueMappers{}) {} + + explicit OpenTelemetrySinkBackend(const ValueMappers &) noexcept; + + void consume(const boost::log::record_view &); + +private: + ValueMappers mappers_; + std::array set_timestamp_if_valid_; + std::array set_thread_id_if_valid_; + std::array set_file_path_if_valid_; + std::array set_func_name_if_valid_; + std::array set_code_line_if_valid_; +}; + +} // namespace boost_log +} // namespace instrumentation +} // namespace opentelemetry diff --git a/instrumentation/boost_log/opentelemetry_boost_log_sink-config.cmake.in b/instrumentation/boost_log/opentelemetry_boost_log_sink-config.cmake.in new file mode 100644 index 000000000..37ba19766 --- /dev/null +++ b/instrumentation/boost_log/opentelemetry_boost_log_sink-config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-target.cmake") + +check_required_components(@PROJECT_NAME@) diff --git a/instrumentation/boost_log/src/sink.cc b/instrumentation/boost_log/src/sink.cc new file mode 100644 index 000000000..2ac02a3f6 --- /dev/null +++ b/instrumentation/boost_log/src/sink.cc @@ -0,0 +1,145 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include +#include +#include + +namespace opentelemetry +{ +namespace instrumentation +{ +namespace boost_log +{ + +static bool ToTimestampDefault(const boost::log::record_view &record, + std::chrono::system_clock::time_point &value) noexcept +{ + using namespace std::chrono; + static constexpr boost::posix_time::ptime kEpochTime(boost::gregorian::date(1970, 1, 1)); + static constexpr boost::posix_time::ptime kInvalid{}; + + const auto ×tamp = + boost::log::extract_or_default(record["TimeStamp"], kInvalid); + value = system_clock::time_point(nanoseconds((timestamp - kEpochTime).total_nanoseconds())); + return timestamp != kInvalid; +} + +static bool ToThreadIdDefault(const boost::log::record_view &record, std::string &value) noexcept +{ + static constexpr boost::log::aux::thread::id kInvalid{}; + + const auto &thread_id = + boost::log::extract_or_default(record["ThreadID"], kInvalid); + + std::stringstream ss; + ss << thread_id; + value = ss.str(); + return thread_id != kInvalid; +} + +static bool ToFileNameDefault(const boost::log::record_view &record, std::string &value) noexcept +{ + value = boost::log::extract_or_default(record["FileName"], std::string{}); + return !value.empty(); +} + +static bool ToFuncNameDefault(const boost::log::record_view &record, std::string &value) noexcept +{ + value = boost::log::extract_or_default(record["FunctionName"], std::string{}); + return !value.empty(); +} + +static bool ToLineNumberDefault(const boost::log::record_view &record, int &value) noexcept +{ + static constexpr int kInvalid = -1; + value = boost::log::extract_or_default(record["LineNumber"], kInvalid); + return value != kInvalid; +} + +OpenTelemetrySinkBackend::OpenTelemetrySinkBackend(const ValueMappers &mappers) noexcept +{ + mappers_.ToSeverity = + mappers.ToSeverity ? mappers.ToSeverity : [](const boost::log::record_view &record) { + return levelToSeverity(boost::log::extract_or_default(record["Severity"], -1)); + }; + mappers_.ToTimeStamp = mappers.ToTimeStamp ? mappers.ToTimeStamp : ToTimestampDefault; + mappers_.ToThreadId = mappers.ToThreadId ? mappers.ToThreadId : ToThreadIdDefault; + mappers_.ToCodeFile = mappers.ToCodeFile ? mappers.ToCodeFile : ToFileNameDefault; + mappers_.ToCodeFunc = mappers.ToCodeFunc ? mappers.ToCodeFunc : ToFuncNameDefault; + mappers_.ToCodeLine = mappers.ToCodeLine ? mappers.ToCodeLine : ToLineNumberDefault; + + using namespace opentelemetry::trace::SemanticConventions; + using opentelemetry::logs::LogRecord; + using timestamp_t = std::chrono::system_clock::time_point; + + set_timestamp_if_valid_ = {[](LogRecord *, const timestamp_t &) {}, + [](LogRecord *log_record, const timestamp_t ×tamp) { + log_record->SetTimestamp(timestamp); + }}; + + set_thread_id_if_valid_ = {[](LogRecord *, const std::string &) {}, + [](LogRecord *log_record, const std::string &thread_id) { + log_record->SetAttribute(kThreadId, thread_id); + }}; + + set_file_path_if_valid_ = {[](LogRecord *, const std::string &) {}, + [](LogRecord *log_record, const std::string &file_name) { + log_record->SetAttribute(kCodeFilepath, file_name); + }}; + + set_func_name_if_valid_ = {[](LogRecord *, const std::string &) {}, + [](LogRecord *log_record, const std::string &func_name) { + log_record->SetAttribute(kCodeFunction, func_name); + }}; + + set_code_line_if_valid_ = {[](LogRecord *, int) {}, + [](LogRecord *log_record, int code_line) { + log_record->SetAttribute(kCodeLineno, code_line); + }}; +} + +void OpenTelemetrySinkBackend::consume(const boost::log::record_view &record) +{ + static constexpr auto kLoggerName = "Boost logger"; + static constexpr auto kLibraryName = "Boost.Log"; + + auto provider = opentelemetry::logs::Provider::GetLoggerProvider(); + auto logger = provider->GetLogger(kLoggerName, kLibraryName, libraryVersion()); + auto log_record = logger->CreateLogRecord(); + + if (log_record) + { + log_record->SetBody( + boost::log::extract_or_default(record["Message"], std::string{})); + log_record->SetSeverity(mappers_.ToSeverity(record)); + + std::chrono::system_clock::time_point timestamp; + set_timestamp_if_valid_[mappers_.ToTimeStamp(record, timestamp)](log_record.get(), timestamp); + + std::string thread_id; + set_thread_id_if_valid_[mappers_.ToThreadId(record, thread_id)](log_record.get(), thread_id); + + std::string file_name; + set_file_path_if_valid_[mappers_.ToCodeFile(record, file_name)](log_record.get(), file_name); + + std::string func_name; + set_func_name_if_valid_[mappers_.ToCodeFunc(record, func_name)](log_record.get(), func_name); + + int code_line; + set_code_line_if_valid_[mappers_.ToCodeLine(record, code_line)](log_record.get(), code_line); + + logger->EmitLogRecord(std::move(log_record)); + } +} + +} // namespace boost_log +} // namespace instrumentation +} // namespace opentelemetry diff --git a/instrumentation/boost_log/test/sink_test.cc b/instrumentation/boost_log/test/sink_test.cc new file mode 100644 index 000000000..5b61b538a --- /dev/null +++ b/instrumentation/boost_log/test/sink_test.cc @@ -0,0 +1,503 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace common = opentelemetry::common; +namespace instr = opentelemetry::instrumentation; +namespace nostd = opentelemetry::nostd; +namespace logs_api = opentelemetry::logs; +namespace trace_api = opentelemetry::trace; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Return; + +struct LogRecordMock final : public logs_api::LogRecord +{ + MOCK_METHOD(void, SetTimestamp, (common::SystemTimestamp), (noexcept, override)); + MOCK_METHOD(void, SetObservedTimestamp, (common::SystemTimestamp), (noexcept, override)); + MOCK_METHOD(void, SetSeverity, (logs_api::Severity), (noexcept, override)); + MOCK_METHOD(void, SetBody, (const common::AttributeValue &), (noexcept, override)); + MOCK_METHOD(void, + SetAttribute, + (nostd::string_view, const common::AttributeValue &), + (noexcept, override)); + MOCK_METHOD(void, SetEventId, (int64_t, nostd::string_view), (noexcept, override)); + MOCK_METHOD(void, SetTraceId, (const trace_api::TraceId &), (noexcept, override)); + MOCK_METHOD(void, SetSpanId, (const trace_api::SpanId &), (noexcept, override)); + MOCK_METHOD(void, SetTraceFlags, (const trace_api::TraceFlags &), (noexcept, override)); +}; + +struct LoggerMock final : public logs_api::Logger +{ + MOCK_METHOD(const nostd::string_view, GetName, (), (noexcept, override)); + MOCK_METHOD(nostd::unique_ptr, CreateLogRecord, (), (noexcept, override)); + MOCK_METHOD(void, + EmitLogRecord, + (nostd::unique_ptr &&), + (noexcept, override)); +}; + +struct LoggerProviderMock final : public logs_api::LoggerProvider +{ + MOCK_METHOD(nostd::shared_ptr, + GetLogger, + (nostd::string_view, + nostd::string_view, + nostd::string_view, + nostd::string_view, + const common::KeyValueIterable &), + (override)); +}; + +class OpenTelemetrySinkTest : public testing::Test +{ +protected: + void SetUp() override + { + using boost::log::sinks::synchronous_sink; + using opentelemetry::instrumentation::boost_log::OpenTelemetrySinkBackend; + auto backend = boost::make_shared(); + auto sink = boost::make_shared>(backend); + boost::log::core::get()->add_sink(sink); + boost::log::add_common_attributes(); + } + + void TearDown() override { boost::log::core::get()->remove_all_sinks(); } +}; + +TEST_F(OpenTelemetrySinkTest, LevelToSeverity) +{ + namespace Level = boost::log::trivial; + using logs_api::Severity; + using ots = instr::boost_log::OpenTelemetrySinkBackend; + + ASSERT_TRUE(Severity::kFatal == ots::levelToSeverity(Level::fatal)); + ASSERT_TRUE(Severity::kError == ots::levelToSeverity(Level::error)); + ASSERT_TRUE(Severity::kWarn == ots::levelToSeverity(Level::warning)); + ASSERT_TRUE(Severity::kInfo == ots::levelToSeverity(Level::info)); + ASSERT_TRUE(Severity::kDebug == ots::levelToSeverity(Level::debug)); + ASSERT_TRUE(Severity::kTrace == ots::levelToSeverity(Level::trace)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::lowest())); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::lowest() + 1)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(-42)); + ASSERT_TRUE(Severity::kTrace == ots::levelToSeverity(0)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(42)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::max() - 1)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::max())); +} + +TEST_F(OpenTelemetrySinkTest, Log_Success) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + auto logrecord_mock = new LogRecordMock(); + auto logrecord_ptr = nostd::unique_ptr(logrecord_mock); + + boost::log::sources::severity_logger logger; + auto pre_log = std::chrono::system_clock::now(); + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll(Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view library_version, nostd::string_view, + const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "Boost logger"); + ASSERT_EQ(library_name, "Boost.Log"); + ASSERT_EQ(library_version, + instr::boost_log::OpenTelemetrySinkBackend::libraryVersion()); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(std::move(logrecord_ptr))); + EXPECT_CALL(*logrecord_mock, SetSeverity(_)).WillOnce(Invoke([](logs_api::Severity severity) { + ASSERT_TRUE(severity == logs_api::Severity::kInfo); + })); + EXPECT_CALL(*logrecord_mock, SetBody(_)) + .WillOnce(Invoke([](const common::AttributeValue &message) { + ASSERT_TRUE(nostd::holds_alternative(message)); + ASSERT_EQ(nostd::get(message), "test message"); + })); + EXPECT_CALL(*logrecord_mock, SetTimestamp(_)) + .WillOnce(Invoke([pre_log](common::SystemTimestamp timestamp) { + auto post_log = std::chrono::system_clock::now(); + ASSERT_TRUE(timestamp.time_since_epoch() >= pre_log.time_since_epoch()); + ASSERT_TRUE(timestamp.time_since_epoch() <= post_log.time_since_epoch()); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.lineno"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &line_number) { + ASSERT_TRUE(nostd::holds_alternative(line_number)); + ASSERT_GE(nostd::get(line_number), 0); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.filepath"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &file_name) { + ASSERT_TRUE(nostd::holds_alternative(file_name)); + ASSERT_TRUE(std::string(nostd::get(file_name)).find("sink_test.cc") != + std::string::npos); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.function"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &func_name) { + ASSERT_TRUE(nostd::holds_alternative(func_name)); + ASSERT_TRUE(nostd::get(func_name) == "TestBody"); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("thread.id"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &thread_id) { + ASSERT_TRUE(nostd::holds_alternative(thread_id)); + ASSERT_FALSE(nostd::get(thread_id).empty()); + })); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(1); + + BOOST_LOG_SEV(logger, boost::log::trivial::info) + << boost::log::add_value("FileName", __FILE__) + << boost::log::add_value("FunctionName", __FUNCTION__) + << boost::log::add_value("LineNumber", __LINE__) << "test message"; +} + +TEST_F(OpenTelemetrySinkTest, Log_Failure) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + boost::log::sources::severity_logger logger; + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll(Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view library_version, nostd::string_view, + const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "Boost logger"); + ASSERT_EQ(library_name, "Boost.Log"); + ASSERT_EQ(library_version, + instr::boost_log::OpenTelemetrySinkBackend::libraryVersion()); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(nullptr)); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(0); + + BOOST_LOG_SEV(logger, boost::log::trivial::info) << "test message"; +} + +TEST_F(OpenTelemetrySinkTest, Log_WithoutSeverity) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + auto logrecord_mock = new LogRecordMock(); + auto logrecord_ptr = nostd::unique_ptr(logrecord_mock); + + boost::log::sources::logger logger; + auto pre_log = std::chrono::system_clock::now(); + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll(Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view library_version, nostd::string_view, + const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "Boost logger"); + ASSERT_EQ(library_name, "Boost.Log"); + ASSERT_EQ(library_version, + instr::boost_log::OpenTelemetrySinkBackend::libraryVersion()); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(std::move(logrecord_ptr))); + EXPECT_CALL(*logrecord_mock, SetSeverity(_)).WillOnce(Invoke([](logs_api::Severity severity) { + ASSERT_TRUE(severity == logs_api::Severity::kInvalid); + })); + EXPECT_CALL(*logrecord_mock, SetBody(_)) + .WillOnce(Invoke([](const common::AttributeValue &message) { + ASSERT_TRUE(nostd::holds_alternative(message)); + ASSERT_EQ(nostd::get(message), "no severity"); + })); + EXPECT_CALL(*logrecord_mock, SetTimestamp(_)) + .WillOnce(Invoke([pre_log](common::SystemTimestamp timestamp) { + auto post_log = std::chrono::system_clock::now(); + ASSERT_TRUE(timestamp.time_since_epoch() >= pre_log.time_since_epoch()); + ASSERT_TRUE(timestamp.time_since_epoch() <= post_log.time_since_epoch()); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("thread.id"), _)).Times(1); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(1); + + BOOST_LOG(logger) << "no severity"; +} + +TEST_F(OpenTelemetrySinkTest, Multi_Threaded) +{ + namespace logs_sdk = opentelemetry::sdk::logs; + namespace logs_exp = opentelemetry::exporter::logs; + + // Set up logger provider + auto exporter = logs_exp::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + auto provider = logs_sdk::LoggerProviderFactory::Create(std::move(processor)); + auto provider_ptr = nostd::shared_ptr(provider.release()); + logs_api::Provider::SetLoggerProvider(provider_ptr); + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::cout.rdbuf(); + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); + // Set up logging threads + const auto count = 100UL; + std::vector threads; + threads.reserve(count); + + boost::log::sources::severity_logger logger; + const auto pre_log = std::chrono::system_clock::now().time_since_epoch().count(); + + for (size_t index = 0; index < count; ++index) + { + threads.emplace_back([&logger, index]() { + BOOST_LOG_SEV(logger, boost::log::trivial::info) << "Test message " << index; + }); + } + + for (auto &task : threads) + { + if (task.joinable()) + { + task.join(); + } + } + + const auto post_log = std::chrono::system_clock::now().time_since_epoch().count(); + + // Reset cout's original stringstream buffer + std::cout.rdbuf(original); + // Extract messages with timestamps + const auto field_name_length = 23UL; + std::vector messages; + std::vector timestamps; + std::string str; + + while (std::getline(output, str, '\n')) + { + if (str.find(" timestamp : ") != std::string::npos) + { + timestamps.push_back(std::strtoul(str.substr(field_name_length).c_str(), nullptr, 10)); + } + else if (str.find(" body : ") != std::string::npos) + { + messages.push_back(str.substr(field_name_length)); + } + } + + for (size_t index = 0; index < count; ++index) + { + const auto &message = "Test message " + std::to_string(index); + const auto &entry = std::find(messages.begin(), messages.end(), message); + ASSERT_TRUE(entry != messages.end()) << message; + + const auto offset = std::distance(messages.begin(), entry); + ASSERT_GE(timestamps[offset], pre_log); + ASSERT_LE(timestamps[offset], post_log); + } +} + +void SetUpBackendWithDummyMappers() +{ + opentelemetry::instrumentation::boost_log::ValueMappers mappers; + mappers.ToSeverity = [](const boost::log::record_view &) { + return opentelemetry::logs::Severity::kTrace4; + }; + mappers.ToTimeStamp = [](const boost::log::record_view &, + std::chrono::system_clock::time_point &) { + // This should prevent SetTimestamp attribute from being called entirely + return false; + }; + mappers.ToCodeLine = [](const boost::log::record_view &, int &code_line) { + code_line = 42; + return true; + }; + mappers.ToCodeFunc = [](const boost::log::record_view &, std::string &func_name) { + func_name = "doFoo"; + return true; + }; + mappers.ToCodeFile = [](const boost::log::record_view &, std::string &file_name) { + file_name = "bar.cpp"; + return true; + }; + mappers.ToThreadId = [](const boost::log::record_view &, std::string &thread_id) { + thread_id = "0x600df457c0d3"; + return true; + }; + + using boost::log::sinks::synchronous_sink; + using opentelemetry::instrumentation::boost_log::OpenTelemetrySinkBackend; + auto backend = boost::make_shared(mappers); + auto sink = boost::make_shared>(backend); + boost::log::core::get()->add_sink(sink); +} + +TEST(OpenTelemetrySinkTestSuite, CustomMappers) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + auto logrecord_mock = new LogRecordMock(); + auto logrecord_ptr = nostd::unique_ptr(logrecord_mock); + + SetUpBackendWithDummyMappers(); + boost::log::sources::severity_logger logger; + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll(Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view library_version, nostd::string_view, + const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "Boost logger"); + ASSERT_EQ(library_name, "Boost.Log"); + ASSERT_EQ(library_version, + instr::boost_log::OpenTelemetrySinkBackend::libraryVersion()); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(std::move(logrecord_ptr))); + EXPECT_CALL(*logrecord_mock, SetSeverity(_)).WillOnce(Invoke([](logs_api::Severity severity) { + ASSERT_TRUE(severity == logs_api::Severity::kTrace4); + })); + EXPECT_CALL(*logrecord_mock, SetBody(_)) + .WillOnce(Invoke([](const common::AttributeValue &message) { + ASSERT_TRUE(nostd::holds_alternative(message)); + ASSERT_EQ(nostd::get(message), "custom mappers"); + })); + EXPECT_CALL(*logrecord_mock, SetTimestamp(_)).Times(0); // Intended - test returning false + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.lineno"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &line_number) { + ASSERT_TRUE(nostd::holds_alternative(line_number)); + ASSERT_EQ(nostd::get(line_number), 42); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.filepath"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &file_name) { + ASSERT_TRUE(nostd::holds_alternative(file_name)); + ASSERT_TRUE(nostd::get(file_name) == "bar.cpp"); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.function"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &func_name) { + ASSERT_TRUE(nostd::holds_alternative(func_name)); + ASSERT_TRUE(nostd::get(func_name) == "doFoo"); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("thread.id"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &thread_id) { + ASSERT_TRUE(nostd::holds_alternative(thread_id)); + ASSERT_TRUE(nostd::get(thread_id) == "0x600df457c0d3"); + })); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(1); + + BOOST_LOG(logger) << "custom mappers"; + boost::log::core::get()->remove_all_sinks(); +} + +enum class CustomSeverity +{ + kRed, + kOrange, + kYellow, + kGreen, + kBlue, + kIndigo, + kViolet +}; + +class CustomSeverityTest + : public ::testing::TestWithParam> +{ +protected: + void SetUp() override + { + opentelemetry::instrumentation::boost_log::ValueMappers mappers; + mappers.ToSeverity = [](const boost::log::record_view &record) { + if (const auto &result = boost::log::extract(record["Severity"])) + { + switch (result.get()) + { + using opentelemetry::logs::Severity; + + case CustomSeverity::kRed: + return Severity::kFatal; + case CustomSeverity::kOrange: + return Severity::kError; + case CustomSeverity::kYellow: + return Severity::kWarn; + case CustomSeverity::kGreen: + return Severity::kInfo; + case CustomSeverity::kBlue: + return Severity::kDebug; + case CustomSeverity::kIndigo: + return Severity::kTrace; + default: + return Severity::kInvalid; + } + } + + return opentelemetry::logs::Severity::kInvalid; + }; + + using boost::log::sinks::synchronous_sink; + using opentelemetry::instrumentation::boost_log::OpenTelemetrySinkBackend; + auto backend = boost::make_shared(mappers); + auto sink = boost::make_shared>(backend); + boost::log::core::get()->add_sink(sink); + } + + void TearDown() override { boost::log::core::get()->remove_all_sinks(); } +}; + +TEST_P(CustomSeverityTest, LevelMapping) +{ + const auto input_level = std::get<0>(GetParam()); + const auto expected_level = std::get<1>(GetParam()); + + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + auto logrecord_mock = new LogRecordMock(); + auto logrecord_ptr = nostd::unique_ptr(logrecord_mock); + boost::log::sources::severity_logger logger; + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)).WillOnce(Return(logger_ptr)); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(std::move(logrecord_ptr))); + EXPECT_CALL(*logrecord_mock, SetSeverity(_)) + .WillOnce(Invoke([expected_level](logs_api::Severity severity) { + ASSERT_TRUE(severity == expected_level); + })); + EXPECT_CALL(*logrecord_mock, SetTimestamp(_)).Times(1); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("thread.id"), _)).Times(1); + EXPECT_CALL(*logrecord_mock, SetBody(_)).Times(1); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(1); + + BOOST_LOG_SEV(logger, input_level); +} + +INSTANTIATE_TEST_SUITE_P( + OpenTelemetrySinkTestSuite, + CustomSeverityTest, + ::testing::Values( + std::make_tuple(CustomSeverity::kRed, opentelemetry::logs::Severity::kFatal), + std::make_tuple(CustomSeverity::kOrange, opentelemetry::logs::Severity::kError), + std::make_tuple(CustomSeverity::kYellow, opentelemetry::logs::Severity::kWarn), + std::make_tuple(CustomSeverity::kGreen, opentelemetry::logs::Severity::kInfo), + std::make_tuple(CustomSeverity::kBlue, opentelemetry::logs::Severity::kDebug), + std::make_tuple(CustomSeverity::kIndigo, opentelemetry::logs::Severity::kTrace), + std::make_tuple(CustomSeverity::kViolet, opentelemetry::logs::Severity::kInvalid))); From 03d95fb412d21c19ba5563796b5cd9dd663e389b Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Wed, 7 Feb 2024 18:36:13 -0800 Subject: [PATCH 11/25] [Geneva] add support to build fluentd exporter as external component of the main repo (#378) --- exporters/fluentd/CMakeLists.txt | 68 +++++++++++++++++++------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/exporters/fluentd/CMakeLists.txt b/exporters/fluentd/CMakeLists.txt index 61f98a73a..1c3fb66db 100644 --- a/exporters/fluentd/CMakeLists.txt +++ b/exporters/fluentd/CMakeLists.txt @@ -49,10 +49,13 @@ else() message("nlohmann_json package was not found. Cloning from github") endif() -find_package(CURL REQUIRED) -find_package(Threads REQUIRED) +if(MAIN_PROJECT) + find_package(CURL REQUIRED) + find_package(Threads REQUIRED) +endif() include_directories(include) + # create fluentd trace exporter add_library(opentelemetry_exporter_fluentd_trace src/trace/fluentd_exporter.cc src/trace/recordable.cc) @@ -63,6 +66,8 @@ if(MAIN_PROJECT) opentelemetry_exporter_fluentd_trace PUBLIC ${OPENTELEMETRY_CPP_LIBRARIES} INTERFACE nlohmann_json::nlohmann_json) + set_target_properties(opentelemetry_exporter_fluentd_trace + PROPERTIES EXPORT_NAME trace) else() target_link_libraries( opentelemetry_exporter_fluentd_trace @@ -70,9 +75,6 @@ else() INTERFACE nlohmann_json::nlohmann_json) endif() -set_target_properties(opentelemetry_exporter_fluentd_trace - PROPERTIES EXPORT_NAME trace) - # create fluentd logs exporter add_library(opentelemetry_exporter_fluentd_logs src/log/fluentd_exporter.cc @@ -84,14 +86,16 @@ if(MAIN_PROJECT) opentelemetry_exporter_fluentd_logs PUBLIC ${OPENTELEMETRY_CPP_LIBRARIES} INTERFACE nlohmann_json::nlohmann_json) + + set_target_properties(opentelemetry_exporter_fluentd_logs + PROPERTIES EXPORT_NAME logs) else() target_link_libraries( opentelemetry_exporter_fluentd_logs PUBLIC opentelemetry_logs opentelemetry_resources opentelemetry_common INTERFACE nlohmann_json::nlohmann_json) endif() -set_target_properties(opentelemetry_exporter_fluentd_logs PROPERTIES EXPORT_NAME - logs) + if(nlohmann_json_clone) add_dependencies(opentelemetry_exporter_fluentd_trace nlohmann_json::nlohmann_json) @@ -100,27 +104,35 @@ if(nlohmann_json_clone) include_directories(${PROJECT_BINARY_DIR}/include) endif() -add_subdirectory(example) - -install( - TARGETS opentelemetry_exporter_fluentd_trace - EXPORT "${PROJECT_NAME}-target" - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -install( - TARGETS opentelemetry_exporter_fluentd_logs - EXPORT "${PROJECT_NAME}-target" - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -install( - DIRECTORY include/opentelemetry/exporters/fluentd/ - DESTINATION include/opentelemetry/exporters/fluentd/ - FILES_MATCHING - PATTERN "*.h") +if(MAIN_PROJECT) + option(WITH_EXAMPLES "Build examples" ON) +endif() + +if (WITH_EXAMPLES) + add_subdirectory(example) +endif() + +if(OPENTELEMETRY_INSTALL) + install( + TARGETS opentelemetry_exporter_fluentd_trace + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + install( + TARGETS opentelemetry_exporter_fluentd_logs + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + + install( + DIRECTORY include/opentelemetry/exporters/ + DESTINATION include/opentelemetry/exporters/ + FILES_MATCHING + PATTERN "*.h") +endif() if(BUILD_TESTING) include(GoogleTest) From d2518f4b4977ed5e45cc32bcbf548c8bbe83949e Mon Sep 17 00:00:00 2001 From: Alex E <36134278+chusitoo@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:55:13 -0500 Subject: [PATCH 12/25] [Logs SDK] Log Appender for glog (#358) --- .clang-format | 122 ++++----- instrumentation/README.md | 1 + instrumentation/glog/CMakeLists.txt | 116 ++++++++ instrumentation/glog/README.md | 149 +++++++++++ instrumentation/glog/example/main.cc | 93 +++++++ .../opentelemetry/instrumentation/glog/sink.h | 63 +++++ .../opentelemetry_glog_sink-config.cmake.in | 5 + instrumentation/glog/src/sink.cc | 65 +++++ instrumentation/glog/test/sink_test.cc | 251 ++++++++++++++++++ 9 files changed, 804 insertions(+), 61 deletions(-) create mode 100644 instrumentation/glog/CMakeLists.txt create mode 100644 instrumentation/glog/README.md create mode 100644 instrumentation/glog/example/main.cc create mode 100644 instrumentation/glog/include/opentelemetry/instrumentation/glog/sink.h create mode 100644 instrumentation/glog/opentelemetry_glog_sink-config.cmake.in create mode 100644 instrumentation/glog/src/sink.cc create mode 100644 instrumentation/glog/test/sink_test.cc diff --git a/.clang-format b/.clang-format index 638d905c4..2640295e6 100644 --- a/.clang-format +++ b/.clang-format @@ -1,61 +1,61 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 - -# See Clang docs: http://clang.llvm.org/docs/ClangFormatStyleOptions.html -BasedOnStyle: Chromium - -# Allow double brackets such as std::vector>. -Standard: Cpp11 - -# Indent 2 spaces at a time. -IndentWidth: 2 - -# Keep lines under 100 columns long. -ColumnLimit: 100 - -# Always break before braces -BreakBeforeBraces: Custom -BraceWrapping: -# TODO(lujc) wait for clang-format-9 support in Chromium tools -# AfterCaseLabel: true - AfterClass: true - AfterControlStatement: true - AfterEnum: true - AfterFunction: true - AfterNamespace: true - AfterStruct: true - AfterUnion: true - BeforeCatch: true - BeforeElse: true - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyRecord: false - SplitEmptyNamespace: false - - # Keeps extern "C" blocks unindented. - AfterExternBlock: false - -# Indent case labels. -IndentCaseLabels: true - -# Right-align pointers and references -PointerAlignment: Right - -# ANGLE likes to align things as much as possible. -AlignOperands: true -AlignConsecutiveAssignments: true - -# Use 2 space negative offset for access modifiers -AccessModifierOffset: -2 - -# TODO(jmadill): Decide if we want this on. Doesn't have an "all or none" mode. -AllowShortCaseLabelsOnASingleLine: false - -# Useful for spacing out functions in classes -KeepEmptyLinesAtTheStartOfBlocks: true - -# Indent nested PP directives. -IndentPPDirectives: AfterHash - -# Include blocks style -IncludeBlocks: Preserve +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +# See Clang docs: http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium + +# Allow double brackets such as std::vector>. +Standard: Cpp11 + +# Indent 2 spaces at a time. +IndentWidth: 2 + +# Keep lines under 100 columns long. +ColumnLimit: 100 + +# Always break before braces +BreakBeforeBraces: Custom +BraceWrapping: +# TODO(lujc) wait for clang-format-9 support in Chromium tools +# AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + + # Keeps extern "C" blocks unindented. + AfterExternBlock: false + +# Indent case labels. +IndentCaseLabels: true + +# Right-align pointers and references +PointerAlignment: Right + +# ANGLE likes to align things as much as possible. +AlignOperands: true +AlignConsecutiveAssignments: true + +# Use 2 space negative offset for access modifiers +AccessModifierOffset: -2 + +# TODO(jmadill): Decide if we want this on. Doesn't have an "all or none" mode. +AllowShortCaseLabelsOnASingleLine: false + +# Useful for spacing out functions in classes +KeepEmptyLinesAtTheStartOfBlocks: true + +# Indent nested PP directives. +IndentPPDirectives: AfterHash + +# Include blocks style +IncludeBlocks: Preserve diff --git a/instrumentation/README.md b/instrumentation/README.md index 4eee5dc0a..b089cabe2 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -5,6 +5,7 @@ In this directory you will find instrumentation libraries and modules. | Name | Description | |---|---| | [boost-log](./boost_log) | Boost.Log OpenTelemetry sink backend | +| [glog](./glog) | OpenTelemetry (Google) glog sink | | [httpd](./httpd) | httpd (Apache) OpenTelemetry module | | [nginx](./nginx) | OpenTelemetry nginx module | | [otel-webserver-module](./otel-webserver-module) | The OTEL webserver module comprises of both Apache and Nginx instrumentation. | diff --git a/instrumentation/glog/CMakeLists.txt b/instrumentation/glog/CMakeLists.txt new file mode 100644 index 000000000..26fa246f9 --- /dev/null +++ b/instrumentation/glog/CMakeLists.txt @@ -0,0 +1,116 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.12) + +set(this_target opentelemetry_glog_sink) + +project(${this_target}) + +find_package(opentelemetry-cpp REQUIRED) +find_package(glog REQUIRED) + +add_library(${this_target} src/sink.cc) + +target_compile_options(${this_target} PUBLIC + -Wall -Wextra -Werror -Wpedantic -fPIC +) + +set_target_properties(${this_target} PROPERTIES EXPORT_NAME ${this_target}) + +target_include_directories(${this_target} PUBLIC + $ + $ + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} +) + +target_link_libraries(${this_target} PRIVATE + glog::glog +) + +if(OPENTELEMETRY_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + + install( + TARGETS ${this_target} + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + install( + DIRECTORY include/opentelemetry/instrumentation/glog + DESTINATION include/opentelemetry/instrumentation + FILES_MATCHING + PATTERN "*.h") + + configure_package_config_file( + "${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + NO_CHECK_REQUIRED_COMPONENTS_MACRO) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + + export( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE ${PROJECT_NAME}:: + FILE "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-target.cmake") + + install( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE ${PROJECT_NAME}:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +endif() # OPENTELEMETRY_INSTALL + +if(BUILD_TESTING) + set(testname sink_test) + + include(GoogleTest) + + add_executable(${testname} "test/${testname}.cc") + + target_include_directories(${testname} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} + ) + + target_link_libraries(${testname} PRIVATE + gmock + gtest + glog::glog + opentelemetry-cpp::ostream_log_record_exporter + ${this_target} + ) + + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX intrumentation.glog. + TEST_LIST ${testname} + ) +endif() # BUILD_TESTING + +if(WITH_EXAMPLES) + set(example_exe otel_sink_example) + add_executable(${example_exe} example/main.cc) + set_target_properties(${example_exe} PROPERTIES EXPORT_NAME ${example_exe}) + + target_include_directories(${example_exe} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} + ) + + target_link_libraries(${example_exe} PRIVATE + opentelemetry-cpp::logs + opentelemetry-cpp::trace + opentelemetry-cpp::ostream_log_record_exporter + opentelemetry-cpp::ostream_span_exporter + glog::glog + ${this_target} + ) +endif() # WITH_EXAMPLES diff --git a/instrumentation/glog/README.md b/instrumentation/glog/README.md new file mode 100644 index 000000000..23dfe4b54 --- /dev/null +++ b/instrumentation/glog/README.md @@ -0,0 +1,149 @@ +# glog OpenTelemetry sink + +## Features + +- Supports glog log sink +- Supports OpenTelemetry SDK without any changes + +## Requirements + +- Current release tested only with Ubuntu 20.04.6 LTS +- OpenTelemetry >= v1.12.0 +- glog >= v0.3.5 + +### Usage + +Please see below for [manual build](#build). Otherwise please use one of the [released versions](https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases). + +### Configuration + +The OpenTelemetry sink can be configured in much the same way as any other glog sink. + +This is done by using the pair of macros specifically available for this purpose and pass to them an object of the `OpenTelemetrySink`. This also means that any other macro that was relying of verbose or debug logging will not have an equivalent that can be used with the sink and, consequently, only severity levels FATAL, ERROR, WARNING and INFO are available for use with the sink. + +```cpp +#include + +google::OpenTelemetrySink sink; +LOG_TO_SINK/*_AND_TO_LOGFILE*/(&sink, INFO) << "This message will be processed and logged."; +LOG_TO_SINK_BUT_NOT_TO_LOGFILE(&sink, INFO) << "This message will be processed but not logged."; +``` + +For more details, refer to the [examples](#examples) section. + +## Development + +### Requirements + +- C++14 +- CMake 3.x +- [OpenTelemetry-cpp](https://github.com/open-telemetry/opentelemetry-cpp) +- [glog](https://github.com/google/glog) +- vcpkg **_(optional)_** + +### Build +As a preparation step, both dependencies need to be built and available in the development environment. This can be a manual build, by following the instructions for the corresponding package, or one could opt to use a package management system such as _vcpkg_ or _conan_. + +Assuming the packages are available on the system, configure CMake as usual: + +```bash +mkdir build +cd build +cmake [path/to/opentelemetry-cpp-contrib]/instrumentation/glog -DBUILD_SHARED_LIBS=ON +make +``` + +Optionally, if the packages were provided via vcpkg, pass in to the _cmake_ command above the flag `-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake` where VCPKG_ROOT is where vcpkg was installed. + +Now, simply link the target source (i.e., main.cc in the example below) against _glog_ and _opentelemetry_glog_sink_: + +```bash +g++ main.cc -lglog -lopentelemetry_glog_sink -o main +``` + +### Installation ### + +When configuring the build, if the flag `-DOPENTELEMETRY_INSTALL=ON` is passed, CMake will ensure to set up the install scripts. Once the build succeeds, running `make install` will make the sink header(s) and library available under the usr include and lib directories, respectively. + +### Testing + +A small suite of unit tests is available under the `test` directory. It can be enabled by passing `-DBUILD_TESTING=ON` when configuring CMake, which will generate an executable called **sink_test**. + +### Examples + +An example executable is available to test the functionality of the sink. For ease of setup, it uses the _OStreamLogRecordExporter_ to display the contents of the intercepted glog message as an OTel _LogRecord_. It can be generated by adding `-DWITH_EXAMPLES=ON` when configuring CMake, which will ultimately produce the **otel_sink_example** executable. + +Running `./otel_sink_example` would produce an output similar to below: + +``` +{ + timestamp : 1703883948145567000 + observed_timestamp : 1703883948145613889 + severity_num : 9 + severity_text : INFO + body : This message will be processed and logged. + resource : + service.name: unknown_service + telemetry.sdk.version: 1.12.0 + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + attributes : + code.lineno: 77 + code.filepath: /otel-contrib/instrumentation/glog/example/main.cc + event_id : 0 + event_name : + trace_id : 7753a4ce0faf85dcae9fbcfb4b14a63c + span_id : 9e2a19e324de6986 + trace_flags : 01 + scope : + name : glog + version : + schema_url : + attributes : +} +{ + timestamp : 1703883948145762000 + observed_timestamp : 1703883948145776966 + severity_num : 9 + severity_text : INFO + body : This message will be processed but not logged. + resource : + service.name: unknown_service + telemetry.sdk.version: 1.12.0 + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + attributes : + code.lineno: 78 + code.filepath: /otel-contrib/instrumentation/glog/example/main.cc + event_id : 0 + event_name : + trace_id : 7753a4ce0faf85dcae9fbcfb4b14a63c + span_id : 9e2a19e324de6986 + trace_flags : 01 + scope : + name : glog + version : + schema_url : + attributes : +} +============================== +Contents of log file ./otel_sink_example.INFO: +... +I20231229 21:05:48.143756 23775 main.cc:72] This message will be ignored and logged by default. +I20231229 21:05:48.145527 23775 main.cc:73] This message will be ignored but logged. +I20231229 21:05:48.145567 23775 main.cc:77] This message will be processed and logged. + +============================== +``` + +The above excerpt shows which logs are included in the log file and which ones are processed by Opentelemetry: + +- For instance, calling `LOG(INFO)` will output "**This message will be processed and logged.**" to console and to file but will not be intercepted by the log sink. + +- Similarly, calling `LOG_TO_SINK(nullptr, INFO)` will output "**This message will be ignored but logged.**" onto the log file but will be ignored otherwise since the sink provided was a null pointer. + +- However, calling `LOG_TO_SINK(&sink, INFO)` will output "**This message will be processed and logged.**" and will also be intercepted by the OTel sink. + +- Finally, calling `LOG_TO_SINK_BUT_NOT_TO_LOGFILE(&sink, INFO)` will produce "**This message will be processed but not logged.**" but will not be output to the log file. + +This example will also output a span with the same ID as the log record(s), to showcase how the two signals can be correlated via trace and/or span ID. diff --git a/instrumentation/glog/example/main.cc b/instrumentation/glog/example/main.cc new file mode 100644 index 000000000..a9d366f68 --- /dev/null +++ b/instrumentation/glog/example/main.cc @@ -0,0 +1,93 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace context = opentelemetry::context; +namespace logs_exp = opentelemetry::exporter::logs; +namespace logs_api = opentelemetry::logs; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace trace_exp = opentelemetry::exporter::trace; +namespace trace_api = opentelemetry::trace; +namespace trace_sdk = opentelemetry::sdk::trace; + +int main(int /* argc */, char **argv) +{ + // Set up logger provider + { + using ProviderPtr = opentelemetry::nostd::shared_ptr; + auto exporter = logs_exp::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + auto provider = logs_sdk::LoggerProviderFactory::Create(std::move(processor)); + logs_api::Provider::SetLoggerProvider(ProviderPtr(provider.release())); + } + + // Set up tracer provider + { + using ProviderPtr = opentelemetry::nostd::shared_ptr; + auto exporter = trace_exp::OStreamSpanExporterFactory::Create(); + auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter)); + auto provider = trace_sdk::TracerProviderFactory::Create(std::move(processor)); + trace_api::Provider::SetTracerProvider(ProviderPtr(provider.release())); + } + + // Set up trace, span and context + auto tracer = trace_api::Provider::GetTracerProvider()->GetTracer("log4cxx_library", + OPENTELEMETRY_SDK_VERSION); + auto span = tracer->StartSpan("log4cxx test span"); + auto ctx = context::RuntimeContext::GetCurrent(); + auto new_ctx = ctx.SetValue("active_span", span); + auto token = context::RuntimeContext::Attach(new_ctx); + + // Set up loggers + const auto log_file = std::string(argv[0]).append(".INFO"); + setenv("GLOG_minloglevel", "0", 1); + google::InitGoogleLogging(argv[0]); + google::SetLogDestination(google::INFO, log_file.c_str()); + + LOG(INFO) << "This message will be ignored and logged by default."; + LOG_TO_SINK/*_AND_TO_LOGFILE*/(nullptr, INFO) << "This message will be ignored but logged."; + LOG_TO_SINK_BUT_NOT_TO_LOGFILE(nullptr, INFO) << "This message will be ignored and not logged."; + google::OpenTelemetrySink sink; + LOG_TO_SINK/*_AND_TO_LOGFILE*/(&sink, INFO) << "This message will be processed and logged."; + LOG_TO_SINK_BUT_NOT_TO_LOGFILE(&sink, INFO) << "This message will be processed but not logged."; + + google::ShutdownGoogleLogging(); + + // Display contents of log file + std::ifstream fin(log_file, std::ios::binary); + + if(fin.is_open()) + { + std::cout << "==============================" << std::endl; + std::cout << "Contents of log file " << log_file << ":" << std::endl; + std::cout << fin.rdbuf() << std::endl; + std::cout << "==============================" << std::endl; + } + + return 0; +} diff --git a/instrumentation/glog/include/opentelemetry/instrumentation/glog/sink.h b/instrumentation/glog/include/opentelemetry/instrumentation/glog/sink.h new file mode 100644 index 000000000..afe0b14eb --- /dev/null +++ b/instrumentation/glog/include/opentelemetry/instrumentation/glog/sink.h @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include + +namespace google +{ + +// The LogMessageTime class was introduced sometime in v0.6.0 +// Since there is no versioning available in this lib, we identify it based on the presence of this +// include guard that was added in the required version +#if defined(GLOG_EXPORT_H) +# define GLOG_VERSION_HAS_LOGMESSAGETIME +#endif + +class OpenTelemetrySink : public google::LogSink +{ +public: + static inline opentelemetry::logs::Severity levelToSeverity(int level) noexcept + { + using opentelemetry::logs::Severity; + + switch (level) + { + case google::GLOG_FATAL: + return Severity::kFatal; + case google::GLOG_ERROR: + return Severity::kError; + case google::GLOG_WARNING: + return Severity::kWarn; + case google::GLOG_INFO: + return Severity::kInfo; + default: + return Severity::kInvalid; + } + } + +#if defined(GLOG_VERSION_HAS_LOGMESSAGETIME) + void send(google::LogSeverity, + const char *, + const char *, + int, + const google::LogMessageTime &, + const char *, + size_t) override; +#else + void send(google::LogSeverity, + const char *, + const char *, + int, + const struct ::tm *, + const char *, + size_t) override; +#endif +}; + +} // namespace google diff --git a/instrumentation/glog/opentelemetry_glog_sink-config.cmake.in b/instrumentation/glog/opentelemetry_glog_sink-config.cmake.in new file mode 100644 index 000000000..37ba19766 --- /dev/null +++ b/instrumentation/glog/opentelemetry_glog_sink-config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-target.cmake") + +check_required_components(@PROJECT_NAME@) diff --git a/instrumentation/glog/src/sink.cc b/instrumentation/glog/src/sink.cc new file mode 100644 index 000000000..dfa5bb5cf --- /dev/null +++ b/instrumentation/glog/src/sink.cc @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include + +namespace google +{ + +#if defined(GLOG_VERSION_HAS_LOGMESSAGETIME) +void OpenTelemetrySink::send(google::LogSeverity severity, + const char *full_filename, + const char * /* base_filename */, + int line, + const google::LogMessageTime &logmsgtime, + const char *message, + size_t message_len) +{ +#else +void OpenTelemetrySink::send(google::LogSeverity severity, + const char *full_filename, + const char * /* base_filename */, + int line, + const struct std::tm * /* time_tm */, + const char *message, + size_t message_len) +{ + // Compensate for the lack of precision in older versions + const auto timestamp = std::chrono::system_clock::now(); +#endif + + static constexpr auto kLoggerName = "Google logger"; + static constexpr auto kLibraryName = "glog"; + + auto provider = opentelemetry::logs::Provider::GetLoggerProvider(); + auto logger = provider->GetLogger(kLoggerName, kLibraryName); + auto log_record = logger->CreateLogRecord(); + + if (log_record) + { + using namespace opentelemetry::trace::SemanticConventions; + using namespace std::chrono; + +#if defined(GLOG_VERSION_HAS_LOGMESSAGETIME) + static constexpr auto secs_to_msecs = duration_cast(seconds{1}).count(); + const auto timestamp = microseconds(secs_to_msecs * logmsgtime.timestamp() + logmsgtime.usec()); +#endif + + log_record->SetSeverity(levelToSeverity(severity)); + log_record->SetBody(opentelemetry::nostd::string_view(message, message_len)); + log_record->SetTimestamp(system_clock::time_point(timestamp)); + log_record->SetAttribute(kCodeFilepath, full_filename); + log_record->SetAttribute(kCodeLineno, line); + logger->EmitLogRecord(std::move(log_record)); + } +} + +} // namespace google diff --git a/instrumentation/glog/test/sink_test.cc b/instrumentation/glog/test/sink_test.cc new file mode 100644 index 000000000..1329b56a8 --- /dev/null +++ b/instrumentation/glog/test/sink_test.cc @@ -0,0 +1,251 @@ +// /* +// * Copyright The OpenTelemetry Authors +// * SPDX-License-Identifier: Apache-2.0 +// */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; +namespace logs_api = opentelemetry::logs; +namespace trace_api = opentelemetry::trace; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Return; + +struct LogRecordMock final : public logs_api::LogRecord +{ + MOCK_METHOD(void, SetTimestamp, (common::SystemTimestamp), (noexcept, override)); + MOCK_METHOD(void, SetObservedTimestamp, (common::SystemTimestamp), (noexcept, override)); + MOCK_METHOD(void, SetSeverity, (logs_api::Severity), (noexcept, override)); + MOCK_METHOD(void, SetBody, (const common::AttributeValue &), (noexcept, override)); + MOCK_METHOD(void, + SetAttribute, + (nostd::string_view, const common::AttributeValue &), + (noexcept, override)); + MOCK_METHOD(void, SetEventId, (int64_t, nostd::string_view), (noexcept, override)); + MOCK_METHOD(void, SetTraceId, (const trace_api::TraceId &), (noexcept, override)); + MOCK_METHOD(void, SetSpanId, (const trace_api::SpanId &), (noexcept, override)); + MOCK_METHOD(void, SetTraceFlags, (const trace_api::TraceFlags &), (noexcept, override)); +}; + +struct LoggerMock final : public logs_api::Logger +{ + MOCK_METHOD(const nostd::string_view, GetName, (), (noexcept, override)); + MOCK_METHOD(nostd::unique_ptr, CreateLogRecord, (), (noexcept, override)); + MOCK_METHOD(void, + EmitLogRecord, + (nostd::unique_ptr &&), + (noexcept, override)); +}; + +struct LoggerProviderMock final : public logs_api::LoggerProvider +{ + MOCK_METHOD(nostd::shared_ptr, + GetLogger, + (nostd::string_view, + nostd::string_view, + nostd::string_view, + nostd::string_view, + const common::KeyValueIterable &), + (override)); +}; + +class OpenTelemetrySinkTest : public testing::Test +{ +public: + nostd::shared_ptr sink_; + +protected: + void SetUp() override + { + sink_ = nostd::shared_ptr(new google::OpenTelemetrySink()); + } + + void TearDown() override {} +}; + +TEST_F(OpenTelemetrySinkTest, LevelToSeverity) +{ + using logs_api::Severity; + using ots = google::OpenTelemetrySink; + + ASSERT_TRUE(Severity::kFatal == ots::levelToSeverity(google::GLOG_FATAL)); + ASSERT_TRUE(Severity::kError == ots::levelToSeverity(google::GLOG_ERROR)); + ASSERT_TRUE(Severity::kWarn == ots::levelToSeverity(google::GLOG_WARNING)); + ASSERT_TRUE(Severity::kInfo == ots::levelToSeverity(google::GLOG_INFO)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::lowest())); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::lowest() + 1)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(-42)); + ASSERT_TRUE(Severity::kInfo == ots::levelToSeverity(0)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(42)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::max() - 1)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::max())); +} + +TEST_F(OpenTelemetrySinkTest, Log_Success) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + auto logrecord_mock = new LogRecordMock(); + auto logrecord_ptr = nostd::unique_ptr(logrecord_mock); + + auto pre_log = std::chrono::system_clock::now(); + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll( + Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view, nostd::string_view, const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "Google logger"); + ASSERT_EQ(library_name, "glog"); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(std::move(logrecord_ptr))); + EXPECT_CALL(*logrecord_mock, SetSeverity(_)).WillOnce(Invoke([](logs_api::Severity severity) { + ASSERT_TRUE(severity == logs_api::Severity::kInfo); + })); + EXPECT_CALL(*logrecord_mock, SetBody(_)) + .WillOnce(Invoke([](const common::AttributeValue &message) { + ASSERT_TRUE(nostd::holds_alternative(message)); + ASSERT_EQ(nostd::get(message), "test message"); + })); + EXPECT_CALL(*logrecord_mock, SetTimestamp(_)) + .WillOnce(Invoke([pre_log](common::SystemTimestamp timestamp) { + auto post_log = std::chrono::system_clock::now(); + ASSERT_TRUE(timestamp.time_since_epoch() >= pre_log.time_since_epoch()); + ASSERT_TRUE(timestamp.time_since_epoch() <= post_log.time_since_epoch()); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.lineno"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &line_number) { + ASSERT_TRUE(nostd::holds_alternative(line_number)); + ASSERT_GE(nostd::get(line_number), 0); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.filepath"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &file_name) { + ASSERT_TRUE(nostd::holds_alternative(file_name)); + ASSERT_TRUE(std::strstr(nostd::get(file_name), "sink_test.cc") != nullptr); + })); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(1); + + LOG_TO_SINK_BUT_NOT_TO_LOGFILE(sink_.get(), INFO) << "test message"; +} + +TEST_F(OpenTelemetrySinkTest, Log_Failure) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll( + Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view, nostd::string_view, const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "Google logger"); + ASSERT_EQ(library_name, "glog"); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(nullptr)); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(0); + + LOG_TO_SINK_BUT_NOT_TO_LOGFILE(sink_.get(), INFO) << "test message"; +} + +TEST_F(OpenTelemetrySinkTest, Multi_Threaded) +{ + namespace logs_sdk = opentelemetry::sdk::logs; + namespace logs_exp = opentelemetry::exporter::logs; + + // Set up logger provider + auto exporter = logs_exp::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + auto provider = logs_sdk::LoggerProviderFactory::Create(std::move(processor)); + auto provider_ptr = nostd::shared_ptr(provider.release()); + logs_api::Provider::SetLoggerProvider(provider_ptr); + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::cout.rdbuf(); + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); + // Set up logging threads + const auto count = 100UL; + std::vector threads; + threads.reserve(count); + + const auto pre_log = std::chrono::system_clock::now().time_since_epoch().count(); + + for (size_t index = 0; index < count; ++index) + { + threads.emplace_back([this, index]() { + LOG_TO_SINK_BUT_NOT_TO_LOGFILE(sink_.get(), INFO) << "Test message " << index; + }); + } + + for (auto &task : threads) + { + if (task.joinable()) + { + task.join(); + } + } + + const auto post_log = std::chrono::system_clock::now().time_since_epoch().count(); + + // Reset cout's original stringstream buffer + std::cout.rdbuf(original); + // Extract messages with timestamps + const auto field_name_length = 23UL; + std::vector messages; + std::vector timestamps; + std::string str; + + while (std::getline(output, str, '\n')) + { + if (str.find(" timestamp : ") != std::string::npos) + { + timestamps.push_back(std::strtoul(str.substr(field_name_length).c_str(), nullptr, 10)); + } + else if (str.find(" body : ") != std::string::npos) + { + messages.push_back(str.substr(field_name_length)); + } + } + + for (size_t index = 0; index < count; ++index) + { + const auto &message = "Test message " + std::to_string(index); + const auto &entry = std::find(messages.begin(), messages.end(), message); + ASSERT_TRUE(entry != messages.end()) << message; + + const auto offset = std::distance(messages.begin(), entry); + ASSERT_GE(timestamps[offset], pre_log); + ASSERT_LE(timestamps[offset], post_log); + } +} + +int main(int argc, char **argv) +{ + google::InitGoogleLogging(argv[0]); + testing::InitGoogleTest(&argc, argv); + const auto result = RUN_ALL_TESTS(); + google::ShutdownGoogleLogging(); + return result; +} From 4e0714c0c7893576b9858466cecf7e06994a0160 Mon Sep 17 00:00:00 2001 From: Alex E <36134278+chusitoo@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:58:57 -0500 Subject: [PATCH 13/25] [Logs SDK] Log Appender for spdlog (#357) --- instrumentation/README.md | 1 + instrumentation/spdlog/CMakeLists.txt | 117 ++++++++ instrumentation/spdlog/README.md | 113 ++++++++ instrumentation/spdlog/example/main.cc | 74 +++++ .../instrumentation/spdlog/sink.h | 67 +++++ .../opentelemetry_spdlog_sink-config.cmake.in | 5 + instrumentation/spdlog/src/sink.cc | 48 ++++ instrumentation/spdlog/test/sink_test.cc | 257 ++++++++++++++++++ 8 files changed, 682 insertions(+) create mode 100644 instrumentation/spdlog/CMakeLists.txt create mode 100644 instrumentation/spdlog/README.md create mode 100644 instrumentation/spdlog/example/main.cc create mode 100644 instrumentation/spdlog/include/opentelemetry/instrumentation/spdlog/sink.h create mode 100644 instrumentation/spdlog/opentelemetry_spdlog_sink-config.cmake.in create mode 100644 instrumentation/spdlog/src/sink.cc create mode 100644 instrumentation/spdlog/test/sink_test.cc diff --git a/instrumentation/README.md b/instrumentation/README.md index b089cabe2..4133fadac 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -8,4 +8,5 @@ In this directory you will find instrumentation libraries and modules. | [glog](./glog) | OpenTelemetry (Google) glog sink | | [httpd](./httpd) | httpd (Apache) OpenTelemetry module | | [nginx](./nginx) | OpenTelemetry nginx module | +| [spdlog](./spdlog) | OpenTelemetry spdlog sink | | [otel-webserver-module](./otel-webserver-module) | The OTEL webserver module comprises of both Apache and Nginx instrumentation. | diff --git a/instrumentation/spdlog/CMakeLists.txt b/instrumentation/spdlog/CMakeLists.txt new file mode 100644 index 000000000..14e21f3a4 --- /dev/null +++ b/instrumentation/spdlog/CMakeLists.txt @@ -0,0 +1,117 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.12) + +set(this_target opentelemetry_spdlog_sink) + +project(${this_target}) + +find_package(opentelemetry-cpp REQUIRED) +find_package(spdlog REQUIRED) + +add_library(${this_target} src/sink.cc) + +target_compile_options(${this_target} PUBLIC + -Wall -Wextra -Werror -Wpedantic -fPIC +) + +set_target_properties(${this_target} PROPERTIES EXPORT_NAME ${this_target}) + +target_include_directories(${this_target} PUBLIC + $ + $ + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} +) + +target_link_libraries(${this_target} PRIVATE + spdlog::spdlog +) + +if(OPENTELEMETRY_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + + install( + TARGETS ${this_target} + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + install( + DIRECTORY include/opentelemetry/instrumentation/spdlog + DESTINATION include/opentelemetry/instrumentation + FILES_MATCHING + PATTERN "*.h") + + configure_package_config_file( + "${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + NO_CHECK_REQUIRED_COMPONENTS_MACRO) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + + export( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE ${PROJECT_NAME}:: + FILE "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-target.cmake") + + install( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE ${PROJECT_NAME}:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +endif() # OPENTELEMETRY_INSTALL + +if(BUILD_TESTING) + set(testname sink_test) + + include(GoogleTest) + + add_executable(${testname} "test/${testname}.cc") + + target_include_directories(${testname} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} + ) + + target_link_libraries(${testname} PRIVATE + gmock + gtest + gtest_main + spdlog::spdlog + opentelemetry-cpp::ostream_log_record_exporter + ${this_target} + ) + + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX intrumentation.spdlog. + TEST_LIST ${testname} + ) +endif() # BUILD_TESTING + +if(WITH_EXAMPLES) + set(example_exe otel_sink_example) + add_executable(${example_exe} example/main.cc) + set_target_properties(${example_exe} PROPERTIES EXPORT_NAME ${example_exe}) + + target_include_directories(${example_exe} PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} + ) + + target_link_libraries(${example_exe} PRIVATE + opentelemetry-cpp::logs + opentelemetry-cpp::trace + opentelemetry-cpp::ostream_log_record_exporter + opentelemetry-cpp::ostream_span_exporter + spdlog::spdlog + ${this_target} + ) +endif() # WITH_EXAMPLES diff --git a/instrumentation/spdlog/README.md b/instrumentation/spdlog/README.md new file mode 100644 index 000000000..bcad98a4c --- /dev/null +++ b/instrumentation/spdlog/README.md @@ -0,0 +1,113 @@ +# spdlog OpenTelemetry sink + +## Features + +- Supports spdlog sink mechanism both, single- and multi-threaded +- Supports OpenTelemetry SDK without any changes + +## Requirements + +- Current release tested only with Ubuntu 20.04.6 LTS +- OpenTelemetry >= v1.12.0 +- spdlog >= v1.5.0 + +### Usage + +Please see below for [manual build](#build). Otherwise please use one of the [released versions](https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases). + +### Configuration + +The OpenTelemetry sink can be configured in much the same way as any other spdlog sink. + +A common scenario is by creating a logger with the `OpenTelemetrySink` template in its sinks list, either by specifying the single-threaded `opentelemetry_sink_st` or the multi-threaded `opentelemetry_sink_mt` concrete classes, with the following lines of code: + +```cpp +#include + +auto otel_sink = std::make_shared(); +otel_sink->set_level(spdlog::level::info); +auto logger = spdlog::logger("OTelLogger", otel_sink); +``` + +For more details, refer to the [examples](#examples) section. + +## Development + +### Requirements + +- C++14 +- CMake 3.x +- [OpenTelemetry-cpp](https://github.com/open-telemetry/opentelemetry-cpp) +- [spdlog](https://github.com/gabime/spdlog) +- vcpkg **_(optional)_** + +### Build +As a preparation step, both dependencies need to be built and available in the development environment. This can be a manual build, by following the instructions for the corresponding package, or one could opt to use a package management system such as _vcpkg_ or _conan_. + +Assuming the packages are available on the system, configure CMake as usual: + +```bash +mkdir build +cd build +cmake [path/to/opentelemetry-cpp-contrib]/instrumentation/spdlog -DBUILD_SHARED_LIBS=ON +make +``` + +Optionally, if the packages were provided via vcpkg, pass in to the _cmake_ command above the flag `-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake` where VCPKG_ROOT is where vcpkg was installed. + +Now, simply link the target source (i.e., main.cc in the example below) against _spdlog_, possibly also its dependency lib _fmt_, as well as _opentelemetry_spdlog_sink_: + +```bash +g++ main.cc -lfmt -lspdlog -lopentelemetry_spdlog_sink -o main +``` + +### Installation ### + +When configuring the build, if the flag `-DOPENTELEMETRY_INSTALL=ON` is passed, CMake will ensure to set up the install scripts. Once the build succeeds, running `make install` will make the sink header(s) and library available under the usr include and lib directories, respectively. + +### Testing + +A small suite of unit tests is available under the `test` directory. It can be enabled by passing `-DBUILD_TESTING=ON` when configuring CMake, which will generate an executable called **sink_test**. + +### Examples + +An example executable is available to test the functionality of the sink. For ease of setup, it uses the _OStreamLogRecordExporter_ to display the contents of the intercepted spdlog message as an OTel _LogRecord_. It can be generated by adding `-DWITH_EXAMPLES=ON` when configuring CMake, which will ultimately produce the **otel_sink_example** executable. + +Running `./otel_sink_example` would produce an output similar to below: + +``` +[2023-12-27 00:46:09.111] [OTelLogger] [debug] This message will be ignored +[2023-12-27 00:46:09.111] [OTelLogger] [info] This message will be processed +{ + timestamp : 1703637969111736310 + observed_timestamp : 1703637969111813143 + severity_num : 9 + severity_text : INFO + body : This message will be processed + resource : + service.name: unknown_service + telemetry.sdk.version: 1.12.0 + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + attributes : + thread.id: 15644 + code.lineno: 73 + code.filepath: /otel-contrib/instrumentation/spdlog/example/main.cc + event_id : 0 + event_name : + trace_id : d2695462067d7aab7e0ed163d27d54f8 + span_id : 4c2389a6f920572d + trace_flags : 01 + scope : + name : spdlog + version : 1.12.0 + schema_url : + attributes : +} +``` + +The above excerpt shows that the log line containing "**[debug] This message will be ignored**" was not set up to use the OpenTelemetry sink and, thus, did not make it into the log exporter. + +Similarly, the DEBUG log message "**[info] This message will be processed**" was invoked from a logger that was set up to use the OpenTelemetry sink and it was successfully processed by the SDK. + +This example will also output a span with the same ID as the log record, to showcase how the two signals can be correlated via trace and/or span ID. diff --git a/instrumentation/spdlog/example/main.cc b/instrumentation/spdlog/example/main.cc new file mode 100644 index 000000000..f88b740df --- /dev/null +++ b/instrumentation/spdlog/example/main.cc @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace context = opentelemetry::context; +namespace logs_exp = opentelemetry::exporter::logs; +namespace logs_api = opentelemetry::logs; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace trace_exp = opentelemetry::exporter::trace; +namespace trace_api = opentelemetry::trace; +namespace trace_sdk = opentelemetry::sdk::trace; + +int main(int /* argc */, char ** /* argv */) +{ + // Set up logger provider + { + using ProviderPtr = opentelemetry::nostd::shared_ptr; + auto exporter = logs_exp::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + auto provider = logs_sdk::LoggerProviderFactory::Create(std::move(processor)); + logs_api::Provider::SetLoggerProvider(ProviderPtr(provider.release())); + } + + // Set up tracer provider + { + using ProviderPtr = opentelemetry::nostd::shared_ptr; + auto exporter = trace_exp::OStreamSpanExporterFactory::Create(); + auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter)); + auto provider = trace_sdk::TracerProviderFactory::Create(std::move(processor)); + trace_api::Provider::SetTracerProvider(ProviderPtr(provider.release())); + } + + // Set up trace, span and context + auto tracer = trace_api::Provider::GetTracerProvider()->GetTracer("log4cxx_library", + OPENTELEMETRY_SDK_VERSION); + auto span = tracer->StartSpan("log4cxx test span"); + auto ctx = context::RuntimeContext::GetCurrent(); + auto new_ctx = ctx.SetValue("active_span", span); + auto token = context::RuntimeContext::Attach(new_ctx); + + // Set up loggers + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::debug); + auto otel_sink = std::make_shared(); + otel_sink->set_level(spdlog::level::info); + + auto logger = spdlog::logger("OTelLogger", {console_sink, otel_sink}); + logger.set_level(spdlog::level::debug); + + SPDLOG_LOGGER_DEBUG(&logger, "This message will be ignored"); + SPDLOG_LOGGER_INFO(&logger, "This message will be processed"); +} diff --git a/instrumentation/spdlog/include/opentelemetry/instrumentation/spdlog/sink.h b/instrumentation/spdlog/include/opentelemetry/instrumentation/spdlog/sink.h new file mode 100644 index 000000000..5814e09dc --- /dev/null +++ b/instrumentation/spdlog/include/opentelemetry/instrumentation/spdlog/sink.h @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include +#include + +#include + +namespace spdlog +{ +namespace sinks +{ + +template +class OpenTelemetrySink : public spdlog::sinks::base_sink +{ +public: + static const std::string &libraryVersion() + { + static const std::string kLibraryVersion = std::to_string(SPDLOG_VER_MAJOR) + "." + + std::to_string(SPDLOG_VER_MINOR) + "." + + std::to_string(SPDLOG_VER_PATCH); + return kLibraryVersion; + } + + static inline opentelemetry::logs::Severity levelToSeverity(int level) noexcept + { + namespace Level = spdlog::level; + using opentelemetry::logs::Severity; + + switch (level) + { + case Level::critical: + return Severity::kFatal; + case Level::err: + return Severity::kError; + case Level::warn: + return Severity::kWarn; + case Level::info: + return Severity::kInfo; + case Level::debug: + return Severity::kDebug; + case Level::trace: + return Severity::kTrace; + case Level::off: + default: + return Severity::kInvalid; + } + } + +protected: + void sink_it_(const spdlog::details::log_msg &msg) override; + void flush_() override {} +}; + +using opentelemetry_sink_mt = OpenTelemetrySink; +using opentelemetry_sink_st = OpenTelemetrySink; + +} // namespace sinks +} // namespace spdlog diff --git a/instrumentation/spdlog/opentelemetry_spdlog_sink-config.cmake.in b/instrumentation/spdlog/opentelemetry_spdlog_sink-config.cmake.in new file mode 100644 index 000000000..f950c5ed3 --- /dev/null +++ b/instrumentation/spdlog/opentelemetry_spdlog_sink-config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-target.cmake") + +check_required_components(@PROJECT_NAME@) diff --git a/instrumentation/spdlog/src/sink.cc b/instrumentation/spdlog/src/sink.cc new file mode 100644 index 000000000..b9eb9575f --- /dev/null +++ b/instrumentation/spdlog/src/sink.cc @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +namespace spdlog +{ +namespace sinks +{ + +template +void OpenTelemetrySink::sink_it_(const spdlog::details::log_msg &msg) +{ + static constexpr auto kLibraryName = "spdlog"; + + auto provider = opentelemetry::logs::Provider::GetLoggerProvider(); + auto logger = provider->GetLogger(msg.logger_name.data(), kLibraryName, libraryVersion()); + auto log_record = logger->CreateLogRecord(); + + if (log_record) + { + using namespace opentelemetry::trace::SemanticConventions; + + log_record->SetSeverity(levelToSeverity(msg.level)); + log_record->SetBody(opentelemetry::nostd::string_view(msg.payload.data(), msg.payload.size())); + log_record->SetTimestamp(msg.time); + if (!msg.source.empty()) + { + log_record->SetAttribute(kCodeFilepath, msg.source.filename); + log_record->SetAttribute(kCodeLineno, msg.source.line); + } + log_record->SetAttribute(kThreadId, msg.thread_id); + logger->EmitLogRecord(std::move(log_record)); + } +} + +// Explicit instantiation to avoid linker errors +template class OpenTelemetrySink; +template class OpenTelemetrySink; + +} // namespace sinks +} // namespace spdlog diff --git a/instrumentation/spdlog/test/sink_test.cc b/instrumentation/spdlog/test/sink_test.cc new file mode 100644 index 000000000..7108331c1 --- /dev/null +++ b/instrumentation/spdlog/test/sink_test.cc @@ -0,0 +1,257 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; +namespace logs_api = opentelemetry::logs; +namespace trace_api = opentelemetry::trace; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Return; + +struct LogRecordMock final : public logs_api::LogRecord +{ + MOCK_METHOD(void, SetTimestamp, (common::SystemTimestamp), (noexcept, override)); + MOCK_METHOD(void, SetObservedTimestamp, (common::SystemTimestamp), (noexcept, override)); + MOCK_METHOD(void, SetSeverity, (logs_api::Severity), (noexcept, override)); + MOCK_METHOD(void, SetBody, (const common::AttributeValue &), (noexcept, override)); + MOCK_METHOD(void, + SetAttribute, + (nostd::string_view, const common::AttributeValue &), + (noexcept, override)); + MOCK_METHOD(void, SetEventId, (int64_t, nostd::string_view), (noexcept, override)); + MOCK_METHOD(void, SetTraceId, (const trace_api::TraceId &), (noexcept, override)); + MOCK_METHOD(void, SetSpanId, (const trace_api::SpanId &), (noexcept, override)); + MOCK_METHOD(void, SetTraceFlags, (const trace_api::TraceFlags &), (noexcept, override)); +}; + +struct LoggerMock final : public logs_api::Logger +{ + MOCK_METHOD(const nostd::string_view, GetName, (), (noexcept, override)); + MOCK_METHOD(nostd::unique_ptr, CreateLogRecord, (), (noexcept, override)); + MOCK_METHOD(void, + EmitLogRecord, + (nostd::unique_ptr &&), + (noexcept, override)); +}; + +struct LoggerProviderMock final : public logs_api::LoggerProvider +{ + MOCK_METHOD(nostd::shared_ptr, + GetLogger, + (nostd::string_view, + nostd::string_view, + nostd::string_view, + nostd::string_view, + const common::KeyValueIterable &), + (override)); +}; + +class OpenTelemetrySinkTest : public testing::Test +{ +public: + nostd::shared_ptr logger_; + +protected: + void SetUp() override + { + auto otel_sink = std::make_shared(); + otel_sink->set_level(spdlog::level::info); + logger_ = nostd::shared_ptr(new spdlog::logger("OTelLogger", otel_sink)); + logger_->set_level(spdlog::level::info); + } + + void TearDown() override {} +}; + +TEST_F(OpenTelemetrySinkTest, LevelToSeverity) +{ + namespace Level = spdlog::level; + using logs_api::Severity; + using ots = spdlog::sinks::opentelemetry_sink_st; + + ASSERT_TRUE(Severity::kFatal == ots::levelToSeverity(Level::critical)); + ASSERT_TRUE(Severity::kError == ots::levelToSeverity(Level::err)); + ASSERT_TRUE(Severity::kWarn == ots::levelToSeverity(Level::warn)); + ASSERT_TRUE(Severity::kInfo == ots::levelToSeverity(Level::info)); + ASSERT_TRUE(Severity::kDebug == ots::levelToSeverity(Level::debug)); + ASSERT_TRUE(Severity::kTrace == ots::levelToSeverity(Level::trace)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(Level::off)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::lowest())); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::lowest() + 1)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(-42)); + ASSERT_TRUE(Severity::kTrace == ots::levelToSeverity(0)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(42)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::max() - 1)); + ASSERT_TRUE(Severity::kInvalid == ots::levelToSeverity(std::numeric_limits::max())); +} + +TEST_F(OpenTelemetrySinkTest, Log_Success) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + auto logrecord_mock = new LogRecordMock(); + auto logrecord_ptr = nostd::unique_ptr(logrecord_mock); + + auto pre_log = std::chrono::system_clock::now(); + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll(Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view library_version, nostd::string_view, + const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "OTelLogger"); + ASSERT_EQ(library_name, "spdlog"); + ASSERT_EQ(library_version, + spdlog::sinks::opentelemetry_sink_mt::libraryVersion()); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(std::move(logrecord_ptr))); + EXPECT_CALL(*logrecord_mock, SetSeverity(_)).WillOnce(Invoke([](logs_api::Severity severity) { + ASSERT_TRUE(severity == logs_api::Severity::kInfo); + })); + EXPECT_CALL(*logrecord_mock, SetBody(_)) + .WillOnce(Invoke([](const common::AttributeValue &message) { + ASSERT_TRUE(nostd::holds_alternative(message)); + ASSERT_EQ(nostd::get(message), "test message"); + })); + EXPECT_CALL(*logrecord_mock, SetTimestamp(_)) + .WillOnce(Invoke([pre_log](common::SystemTimestamp timestamp) { + auto post_log = std::chrono::system_clock::now(); + ASSERT_TRUE(timestamp.time_since_epoch() >= pre_log.time_since_epoch()); + ASSERT_TRUE(timestamp.time_since_epoch() <= post_log.time_since_epoch()); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.lineno"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &line_number) { + ASSERT_TRUE(nostd::holds_alternative(line_number)); + ASSERT_GE(nostd::get(line_number), 0); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.filepath"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &file_name) { + ASSERT_TRUE(nostd::holds_alternative(file_name)); + ASSERT_TRUE(std::strstr(nostd::get(file_name), "sink_test.cc") != nullptr); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("thread.id"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &thread_id) { + ASSERT_TRUE(nostd::holds_alternative(thread_id)); + ASSERT_GT(nostd::get(thread_id), 0); + })); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(1); + + SPDLOG_LOGGER_INFO(logger_, "test message"); +} + +TEST_F(OpenTelemetrySinkTest, Log_Failure) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll(Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view library_version, nostd::string_view, + const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "OTelLogger"); + ASSERT_EQ(library_name, "spdlog"); + ASSERT_EQ(library_version, + spdlog::sinks::opentelemetry_sink_mt::libraryVersion()); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(nullptr)); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(0); + + SPDLOG_LOGGER_INFO(logger_, "test message"); +} + +TEST_F(OpenTelemetrySinkTest, Multi_Threaded) +{ + namespace logs_sdk = opentelemetry::sdk::logs; + namespace logs_exp = opentelemetry::exporter::logs; + + // Set up logger provider + auto exporter = logs_exp::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + auto provider = logs_sdk::LoggerProviderFactory::Create(std::move(processor)); + auto provider_ptr = nostd::shared_ptr(provider.release()); + logs_api::Provider::SetLoggerProvider(provider_ptr); + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::cout.rdbuf(); + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); + // Set up logging threads + const auto count = 100UL; + std::vector threads; + threads.reserve(count); + + const auto pre_log = std::chrono::system_clock::now().time_since_epoch().count(); + + for (size_t index = 0; index < count; ++index) + { + threads.emplace_back( + [this, index]() { SPDLOG_LOGGER_INFO(logger_, "Test message {}", index); }); + } + + for (auto &task : threads) + { + if (task.joinable()) + { + task.join(); + } + } + + const auto post_log = std::chrono::system_clock::now().time_since_epoch().count(); + + // Reset cout's original stringstream buffer + std::cout.rdbuf(original); + // Extract messages with timestamps + const auto field_name_length = 23UL; + std::vector messages; + std::vector timestamps; + std::string str; + + while (std::getline(output, str, '\n')) + { + if (str.find(" timestamp : ") != std::string::npos) + { + timestamps.push_back(std::strtoul(str.substr(field_name_length).c_str(), nullptr, 10)); + } + else if (str.find(" body : ") != std::string::npos) + { + messages.push_back(str.substr(field_name_length)); + } + } + + for (size_t index = 0; index < count; ++index) + { + const auto &message = "Test message " + std::to_string(index); + const auto &entry = std::find(messages.begin(), messages.end(), message); + ASSERT_TRUE(entry != messages.end()) << message; + + const auto offset = std::distance(messages.begin(), entry); + ASSERT_GE(timestamps[offset], pre_log); + ASSERT_LE(timestamps[offset], post_log); + } +} From 6a7bb023bba5f291839e305778e41eb6a43cfbb4 Mon Sep 17 00:00:00 2001 From: Alex E <36134278+chusitoo@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:00:28 -0500 Subject: [PATCH 14/25] [Logs SDK] Log Appender for log4cxx (#356) --- instrumentation/README.md | 1 + instrumentation/log4cxx/CMakeLists.txt | 134 +++++++++ instrumentation/log4cxx/README.md | 130 +++++++++ instrumentation/log4cxx/example/main.cc | 89 ++++++ .../log4cxx/example/otel-appender.xml | 19 ++ .../instrumentation/log4cxx/appender.h | 71 +++++ ...telemetry_log4cxx_appender-config.cmake.in | 5 + instrumentation/log4cxx/src/appender.cc | 36 +++ instrumentation/log4cxx/test/appender_test.cc | 258 ++++++++++++++++++ 9 files changed, 743 insertions(+) create mode 100644 instrumentation/log4cxx/CMakeLists.txt create mode 100644 instrumentation/log4cxx/README.md create mode 100644 instrumentation/log4cxx/example/main.cc create mode 100644 instrumentation/log4cxx/example/otel-appender.xml create mode 100644 instrumentation/log4cxx/include/opentelemetry/instrumentation/log4cxx/appender.h create mode 100644 instrumentation/log4cxx/opentelemetry_log4cxx_appender-config.cmake.in create mode 100644 instrumentation/log4cxx/src/appender.cc create mode 100644 instrumentation/log4cxx/test/appender_test.cc diff --git a/instrumentation/README.md b/instrumentation/README.md index 4133fadac..eebb9202d 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -7,6 +7,7 @@ In this directory you will find instrumentation libraries and modules. | [boost-log](./boost_log) | Boost.Log OpenTelemetry sink backend | | [glog](./glog) | OpenTelemetry (Google) glog sink | | [httpd](./httpd) | httpd (Apache) OpenTelemetry module | +| [log4cxx](./log4cxx) | Log4cxx (Apache) OpenTelemetry appender | | [nginx](./nginx) | OpenTelemetry nginx module | | [spdlog](./spdlog) | OpenTelemetry spdlog sink | | [otel-webserver-module](./otel-webserver-module) | The OTEL webserver module comprises of both Apache and Nginx instrumentation. | diff --git a/instrumentation/log4cxx/CMakeLists.txt b/instrumentation/log4cxx/CMakeLists.txt new file mode 100644 index 000000000..2ac2afa26 --- /dev/null +++ b/instrumentation/log4cxx/CMakeLists.txt @@ -0,0 +1,134 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.12) + +set(this_target opentelemetry_log4cxx_appender) + +project(${this_target}) + +find_package(opentelemetry-cpp QUIET) + +if (opentelemetry-cpp_FOUND) + message(STATUS "Found opentelemetry-cpp version: ${opentelemetry-cpp_VERSION}") +else() + message(FATAL_ERROR "Could not find opentelemetry-cpp") +endif() + +find_package(log4cxx QUIET) + +if (log4cxx_FOUND) + message(STATUS "Found log4cxx version: ${log4cxx_VERSION}") +else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(LOG4CXX REQUIRED liblog4cxx) +endif() + +add_library(${this_target} src/appender.cc) + +target_compile_options(${this_target} + PUBLIC -Wall -Wextra -Werror -Wpedantic -fPIC +) + +set_target_properties(${this_target} PROPERTIES EXPORT_NAME ${this_target}) + +target_include_directories(${this_target} PUBLIC + $ + $ + $ + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} +) + +target_link_libraries(${this_target} PRIVATE + log4cxx +) + +if(OPENTELEMETRY_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + + install( + TARGETS ${this_target} + EXPORT "${PROJECT_NAME}-target" + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + install( + DIRECTORY include/opentelemetry/instrumentation/log4cxx + DESTINATION include/opentelemetry/instrumentation + FILES_MATCHING + PATTERN "*.h") + + configure_package_config_file( + "${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + NO_CHECK_REQUIRED_COMPONENTS_MACRO) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + + export( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE ${PROJECT_NAME}:: + FILE "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}/${PROJECT_NAME}-target.cmake") + + install( + EXPORT "${PROJECT_NAME}-target" + NAMESPACE ${PROJECT_NAME}:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +endif() # OPENTELEMETRY_INSTALL + +if(BUILD_TESTING) + set(testname appender_test) + + include(GoogleTest) + + add_executable(${testname} "test/${testname}.cc") + + target_include_directories(${testname} PUBLIC + $ + ${CMAKE_CURRENT_LIST_DIR}/include + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} + ) + + target_link_libraries(${testname} PRIVATE + gmock + gtest + gtest_main + log4cxx + opentelemetry-cpp::ostream_log_record_exporter + ${this_target} + ) + + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX intrumentation.log4cxx. + TEST_LIST ${testname} + ) +endif() # BUILD_TESTING + +if(WITH_EXAMPLES) + set(example_exe otel_appender_example) + add_executable(${example_exe} example/main.cc) + set_target_properties(${example_exe} PROPERTIES EXPORT_NAME ${example_exe}) + + target_include_directories(${example_exe} PUBLIC + $ + ${CMAKE_CURRENT_LIST_DIR}/include + ${OPENTELEMETRY_CPP_INCLUDE_DIRS} + ) + + target_link_libraries(${example_exe} PRIVATE + opentelemetry-cpp::logs + opentelemetry-cpp::trace + opentelemetry-cpp::ostream_log_record_exporter + opentelemetry-cpp::ostream_span_exporter + log4cxx + ${this_target} + ) +endif() # WITH_EXAMPLES diff --git a/instrumentation/log4cxx/README.md b/instrumentation/log4cxx/README.md new file mode 100644 index 000000000..48279aff4 --- /dev/null +++ b/instrumentation/log4cxx/README.md @@ -0,0 +1,130 @@ +# Log4cxx (Apache) OpenTelemetry appender + +## Features + +- Supports log4cxx appender both, programatically and via config file +- Supports OpenTelemetry SDK without any changes + +## Requirements + +- Current release tested only with Ubuntu 20.04.6 LTS +- OpenTelemetry >= v1.12.0 +- log4cxx (Apache) >= v1.0.0 + +### Usage + +Please see below for [manual build](#build). Otherwise please use one of the [released versions](https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases). + +### Configuration + +The OpenTelemetry appender can be configured in much the same way as any other log4cxx appender. + +A common scenario is by attaching the `OpenTelemetryAppender` to a logger via a XML config file: + +```xml + + + + + +``` + +The same effect can be achieved programatically with a few lines of code: + +```cpp +#include + +auto some_logger = log4cxx::Logger::getLogger("SomeLogger"); +some_logger->addAppender(std::make_shared()); +some_logger->setAdditivity(false); +``` + +For more details, refer to the [examples](#examples) section. + +## Development + +### Requirements + +- C++14 +- CMake 3.x +- [OpenTelemetry-cpp](https://github.com/open-telemetry/opentelemetry-cpp) +- [Log4cxx](https://github.com/apache/logging-log4cxx) +- vcpkg **_(optional)_** + +### Build +As a preparation step, both dependencies need to be built and available in the development environment. This can be a manual build, by following the instructions for the corresponding package, or one could opt to use a package management system such as _vcpkg_ or _conan_. + +Assuming the packages are available on the system, configure CMake as usual: + +```bash +mkdir build +cd build +cmake [path/to/opentelemetry-cpp-contrib]/instrumentation/log4cxx -DBUILD_SHARED_LIBS=ON +make +``` + +Optionally, if the packages were provided via vcpkg, pass in to the _cmake_ command above the flag `-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake` where VCPKG_ROOT is where vcpkg was installed. + +Now, simply link the target source (i.e., main.cc in the example below) against _log4cxx_ and _opentelemetry_log4cxx_appender_: + +```bash +g++ main.cc -llog4cxx -lopentelemetry_log4cxx_appender -o main +``` + +### Installation ### + +When configuring the build, if the flag `-DOPENTELEMETRY_INSTALL=ON` is passed, CMake will ensure to set up the install scripts. Once the build succeeds, running `make install` will make the appender header(s) and library available under the usr include and lib directories, respectively. + +### Testing + +A small suite of unit tests is available under the `test` directory. It can be enabled by passing `-DBUILD_TESTING=ON` when configuring CMake, which will generate an executable called **appender_test**. + +### Examples + +An example executable is available to test the functionality of the appender. For ease of setup, it uses the _OStreamLogRecordExporter_ to display the contents of the intercepted log4cxx event as an OTel _LogRecord_. It can be generated by adding `-DWITH_EXAMPLES=ON` when configuring CMake, which will ultimately produce the **otel_appender_example** executable. + +By default, this executable configures the appender and logger programatically. +However, it will also accept a XML config file which provides a more flexible approach. An example configuration is available under `example/otel-appender.xml` and can be fed to the program as follows: + +```cpp +./otel_appender_example [path_to_file/]otel-appender.xml +``` + +Both scenarios, whether that be programatically or via configuration file, would produce an output similar to below: + +``` +>>> Setting up appender from /otel-contrib/instrumentation/log4cxx/example/otel-appender.xml +[2023-12-26 07:09:39] root INFO - This message will be ignored +{ + timestamp : 1703574579523570000 + observed_timestamp : 1703574579523660840 + severity_num : 5 + severity_text : DEBUG + body : This message will be processed + resource : + service.name: unknown_service + telemetry.sdk.version: 1.12.0 + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: cpp + attributes : + thread.name: 0x7fae5f373bc0 + code.lineno: 88 + code.filepath: /otel-contrib/instrumentation/log4cxx/example/main.cc + event_id : 0 + event_name : + trace_id : dc62ae8020403633fcce3d50eeb34b3c + span_id : 7eaa73e3d06d78d0 + trace_flags : 01 + scope : + name : log4cxx + version : 1.0.0 + schema_url : + attributes : +} +``` + +The above excerpt shows that the log line containing "**root INFO - This message will be ignored**" was not set up to use the OpenTelemetry appender and, thus, did not make it into the log exporter. + +Similarly, the DEBUG log message "**This message will be processed**" was invoked from a logger that was set up to use the OpenTelemetry appender and it was successfully processed by the SDK. + +This example will also output a span with the same ID as the log record, to showcase how the two signals can be correlated via trace and/or span ID. diff --git a/instrumentation/log4cxx/example/main.cc b/instrumentation/log4cxx/example/main.cc new file mode 100644 index 000000000..3c18d91b3 --- /dev/null +++ b/instrumentation/log4cxx/example/main.cc @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace context = opentelemetry::context; +namespace logs_exp = opentelemetry::exporter::logs; +namespace logs_api = opentelemetry::logs; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace trace_exp = opentelemetry::exporter::trace; +namespace trace_api = opentelemetry::trace; +namespace trace_sdk = opentelemetry::sdk::trace; + +int main(int argc, char **argv) +{ + // Set up logger provider + { + using ProviderPtr = std::shared_ptr; + auto exporter = logs_exp::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + auto provider = logs_sdk::LoggerProviderFactory::Create(std::move(processor)); + logs_api::Provider::SetLoggerProvider(ProviderPtr(provider.release())); + } + + // Set up tracer provider + { + using ProviderPtr = std::shared_ptr; + auto exporter = trace_exp::OStreamSpanExporterFactory::Create(); + auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter)); + auto provider = trace_sdk::TracerProviderFactory::Create(std::move(processor)); + trace_api::Provider::SetTracerProvider(ProviderPtr(provider.release())); + } + + // Set up trace, span and context + auto tracer = trace_api::Provider::GetTracerProvider()->GetTracer("log4cxx_library", + OPENTELEMETRY_SDK_VERSION); + auto span = tracer->StartSpan("log4cxx test span"); + auto ctx = context::RuntimeContext::GetCurrent(); + auto new_ctx = ctx.SetValue("active_span", span); + auto token = context::RuntimeContext::Attach(new_ctx); + + // Set up loggers + auto root_logger = log4cxx::Logger::getRootLogger(); + auto otel_logger = log4cxx::Logger::getLogger("OTelLogger"); + + const auto has_xml_config = argc > 1; + const auto xml_config = has_xml_config ? argv[1] : ""; + + if (has_xml_config) + { + std::cout << ">>> Setting up appender from " << xml_config << std::endl; + log4cxx::xml::DOMConfigurator::configure(xml_config); + } + else + { + std::cout << ">>> Setting up appender programatically" << std::endl; + const auto format = "[%d{yyyy-MM-dd HH:mm:ss}] %c %-5p - %m%n"; + const auto layout_ptr = std::make_shared(format); + root_logger->addAppender(std::make_shared(layout_ptr)); + otel_logger->addAppender(std::make_shared()); + otel_logger->setAdditivity(false); + } + + LOG4CXX_INFO(root_logger, "This message will be ignored"); + LOG4CXX_DEBUG(otel_logger, "This message will be processed"); +} diff --git a/instrumentation/log4cxx/example/otel-appender.xml b/instrumentation/log4cxx/example/otel-appender.xml new file mode 100644 index 000000000..9a557ee6b --- /dev/null +++ b/instrumentation/log4cxx/example/otel-appender.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/instrumentation/log4cxx/include/opentelemetry/instrumentation/log4cxx/appender.h b/instrumentation/log4cxx/include/opentelemetry/instrumentation/log4cxx/appender.h new file mode 100644 index 000000000..3da677156 --- /dev/null +++ b/instrumentation/log4cxx/include/opentelemetry/instrumentation/log4cxx/appender.h @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include +#include +#include + +namespace log4cxx +{ + +class OpenTelemetryAppender : public AppenderSkeleton +{ +public: + DECLARE_LOG4CXX_OBJECT(OpenTelemetryAppender) + BEGIN_LOG4CXX_CAST_MAP() + LOG4CXX_CAST_ENTRY(OpenTelemetryAppender) + LOG4CXX_CAST_ENTRY_CHAIN(AppenderSkeleton) + END_LOG4CXX_CAST_MAP() + + static const std::string& libraryVersion() + { + static const std::string kLibraryVersion = std::to_string(LOG4CXX_VERSION_MAJOR) + "." + + std::to_string(LOG4CXX_VERSION_MINOR) + "." + + std::to_string(LOG4CXX_VERSION_PATCH); + return kLibraryVersion; + } + + static inline opentelemetry::logs::Severity levelToSeverity(int level) noexcept + { + using log4cxx::Level; + using opentelemetry::logs::Severity; + + switch (level) + { + case Level::FATAL_INT: + return Severity::kFatal; + case Level::ERROR_INT: + return Severity::kError; + case Level::WARN_INT: + return Severity::kWarn; + case Level::INFO_INT: + return Severity::kInfo; + case Level::DEBUG_INT: + return Severity::kDebug; + case Level::TRACE_INT: + case Level::ALL_INT: + return Severity::kTrace; + case Level::OFF_INT: + default: + return Severity::kInvalid; + } + } + + OpenTelemetryAppender() = default; + + void close() override {} + + inline bool requiresLayout() const override { return false; } + + void append(const spi::LoggingEventPtr &event, helpers::Pool &) override; +}; + +IMPLEMENT_LOG4CXX_OBJECT(OpenTelemetryAppender) + +} // namespace log4cxx diff --git a/instrumentation/log4cxx/opentelemetry_log4cxx_appender-config.cmake.in b/instrumentation/log4cxx/opentelemetry_log4cxx_appender-config.cmake.in new file mode 100644 index 000000000..37ba19766 --- /dev/null +++ b/instrumentation/log4cxx/opentelemetry_log4cxx_appender-config.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-target.cmake") + +check_required_components(@PROJECT_NAME@) diff --git a/instrumentation/log4cxx/src/appender.cc b/instrumentation/log4cxx/src/appender.cc new file mode 100644 index 000000000..8d221ca3a --- /dev/null +++ b/instrumentation/log4cxx/src/appender.cc @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +namespace log4cxx +{ + +void OpenTelemetryAppender::append(const spi::LoggingEventPtr &event, helpers::Pool &) +{ + static constexpr auto kLibraryName = "log4cxx"; + + auto provider = opentelemetry::logs::Provider::GetLoggerProvider(); + auto logger = provider->GetLogger(event->getLoggerName(), kLibraryName, libraryVersion()); + auto log_record = logger->CreateLogRecord(); + + if (log_record) + { + using namespace opentelemetry::trace::SemanticConventions; + + log_record->SetSeverity(levelToSeverity(event->getLevel()->toInt())); + log_record->SetBody(event->getMessage()); + log_record->SetTimestamp(event->getChronoTimeStamp()); + log_record->SetAttribute(kCodeFilepath, event->getLocationInformation().getFileName()); + log_record->SetAttribute(kCodeLineno, event->getLocationInformation().getLineNumber()); + log_record->SetAttribute(kThreadName, event->getThreadName()); + logger->EmitLogRecord(std::move(log_record)); + } +} + +} // namespace log4cxx diff --git a/instrumentation/log4cxx/test/appender_test.cc b/instrumentation/log4cxx/test/appender_test.cc new file mode 100644 index 000000000..acd589d1a --- /dev/null +++ b/instrumentation/log4cxx/test/appender_test.cc @@ -0,0 +1,258 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; +namespace logs_api = opentelemetry::logs; +namespace trace_api = opentelemetry::trace; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Return; + +struct LogRecordMock final : public logs_api::LogRecord +{ + MOCK_METHOD(void, SetTimestamp, (common::SystemTimestamp), (noexcept, override)); + MOCK_METHOD(void, SetObservedTimestamp, (common::SystemTimestamp), (noexcept, override)); + MOCK_METHOD(void, SetSeverity, (logs_api::Severity), (noexcept, override)); + MOCK_METHOD(void, SetBody, (const common::AttributeValue &), (noexcept, override)); + MOCK_METHOD(void, + SetAttribute, + (nostd::string_view, const common::AttributeValue &), + (noexcept, override)); + MOCK_METHOD(void, SetEventId, (int64_t, nostd::string_view), (noexcept, override)); + MOCK_METHOD(void, SetTraceId, (const trace_api::TraceId &), (noexcept, override)); + MOCK_METHOD(void, SetSpanId, (const trace_api::SpanId &), (noexcept, override)); + MOCK_METHOD(void, SetTraceFlags, (const trace_api::TraceFlags &), (noexcept, override)); +}; + +struct LoggerMock final : public logs_api::Logger +{ + MOCK_METHOD(const nostd::string_view, GetName, (), (noexcept, override)); + MOCK_METHOD(nostd::unique_ptr, CreateLogRecord, (), (noexcept, override)); + MOCK_METHOD(void, + EmitLogRecord, + (nostd::unique_ptr &&), + (noexcept, override)); +}; + +struct LoggerProviderMock final : public logs_api::LoggerProvider +{ + MOCK_METHOD(nostd::shared_ptr, + GetLogger, + (nostd::string_view, + nostd::string_view, + nostd::string_view, + nostd::string_view, + const common::KeyValueIterable &), + (override)); +}; + +class OpenTelemetryAppenderTest : public testing::Test +{ +public: + log4cxx::LoggerPtr logger_; + +protected: + void SetUp() override + { + logger_ = log4cxx::Logger::getLogger("OTelLogger"); + logger_->addAppender(std::make_shared()); + } + + void TearDown() override { logger_->removeAllAppenders(); } +}; + +TEST_F(OpenTelemetryAppenderTest, LevelToSeverity) +{ + using log4cxx::Level; + using logs_api::Severity; + using ota = log4cxx::OpenTelemetryAppender; + + ASSERT_TRUE(Severity::kFatal == ota::levelToSeverity(Level::FATAL_INT)); + ASSERT_TRUE(Severity::kError == ota::levelToSeverity(Level::ERROR_INT)); + ASSERT_TRUE(Severity::kWarn == ota::levelToSeverity(Level::WARN_INT)); + ASSERT_TRUE(Severity::kInfo == ota::levelToSeverity(Level::INFO_INT)); + ASSERT_TRUE(Severity::kDebug == ota::levelToSeverity(Level::DEBUG_INT)); + ASSERT_TRUE(Severity::kTrace == ota::levelToSeverity(Level::TRACE_INT)); + ASSERT_TRUE(Severity::kTrace == ota::levelToSeverity(Level::ALL_INT)); + ASSERT_TRUE(Severity::kInvalid == ota::levelToSeverity(Level::OFF_INT)); + ASSERT_TRUE(Severity::kTrace == ota::levelToSeverity(std::numeric_limits::lowest())); + ASSERT_TRUE(Severity::kInvalid == ota::levelToSeverity(std::numeric_limits::lowest() + 1)); + ASSERT_TRUE(Severity::kInvalid == ota::levelToSeverity(-42)); + ASSERT_TRUE(Severity::kInvalid == ota::levelToSeverity(0)); + ASSERT_TRUE(Severity::kInvalid == ota::levelToSeverity(42)); + ASSERT_TRUE(Severity::kInvalid == ota::levelToSeverity(std::numeric_limits::max() - 1)); + ASSERT_TRUE(Severity::kInvalid == ota::levelToSeverity(std::numeric_limits::max())); +} + +TEST_F(OpenTelemetryAppenderTest, Log_Success) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + auto logrecord_mock = new LogRecordMock(); + auto logrecord_ptr = nostd::unique_ptr(logrecord_mock); + + auto pre_log = std::chrono::system_clock::now(); + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll(Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view library_version, nostd::string_view, + const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "OTelLogger"); + ASSERT_EQ(library_name, "log4cxx"); + ASSERT_EQ(library_version, + log4cxx::OpenTelemetryAppender::libraryVersion()); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(std::move(logrecord_ptr))); + EXPECT_CALL(*logrecord_mock, SetSeverity(_)).WillOnce(Invoke([](logs_api::Severity severity) { + ASSERT_TRUE(severity == logs_api::Severity::kInfo); + })); + EXPECT_CALL(*logrecord_mock, SetBody(_)) + .WillOnce(Invoke([](const common::AttributeValue &message) { + ASSERT_TRUE(nostd::holds_alternative(message)); + ASSERT_EQ(nostd::get(message), "test message"); + })); + EXPECT_CALL(*logrecord_mock, SetTimestamp(_)) + .WillOnce(Invoke([pre_log](common::SystemTimestamp timestamp) { + auto post_log = std::chrono::system_clock::now(); + ASSERT_TRUE(timestamp.time_since_epoch() >= pre_log.time_since_epoch()); + ASSERT_TRUE(timestamp.time_since_epoch() <= post_log.time_since_epoch()); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.lineno"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &line_number) { + ASSERT_TRUE(nostd::holds_alternative(line_number)); + ASSERT_GE(nostd::get(line_number), 0); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("code.filepath"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &file_name) { + ASSERT_TRUE(nostd::holds_alternative(file_name)); + ASSERT_TRUE(std::strstr(nostd::get(file_name), "appender_test.cc") != + nullptr); + })); + EXPECT_CALL(*logrecord_mock, SetAttribute(nostd::string_view("thread.name"), _)) + .WillOnce(Invoke([](nostd::string_view, const common::AttributeValue &thread_name) { + ASSERT_TRUE(nostd::holds_alternative(thread_name)); + ASSERT_FALSE(nostd::get(thread_name).empty()); + })); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(1); + + LOG4CXX_INFO(logger_, "test message"); +} + +TEST_F(OpenTelemetryAppenderTest, Log_Failure) +{ + auto provider_mock = new LoggerProviderMock(); + logs_api::Provider::SetLoggerProvider(nostd::shared_ptr(provider_mock)); + auto logger_mock = new LoggerMock(); + auto logger_ptr = nostd::shared_ptr(logger_mock); + auto logrecord_mock = new LogRecordMock(); + auto logrecord_ptr = nostd::unique_ptr(logrecord_mock); + + EXPECT_CALL(*provider_mock, GetLogger(_, _, _, _, _)) + .WillOnce(DoAll(Invoke([](nostd::string_view logger_name, nostd::string_view library_name, + nostd::string_view library_version, nostd::string_view, + const common::KeyValueIterable &) { + ASSERT_EQ(logger_name, "OTelLogger"); + ASSERT_EQ(library_name, "log4cxx"); + ASSERT_EQ(library_version, + log4cxx::OpenTelemetryAppender::libraryVersion()); + }), + Return(logger_ptr))); + EXPECT_CALL(*logger_mock, CreateLogRecord()).WillOnce(Return(nullptr)); + EXPECT_CALL(*logger_mock, EmitLogRecord(_)).Times(0); + + LOG4CXX_INFO(logger_, "test message"); +} + +TEST_F(OpenTelemetryAppenderTest, Multi_Threaded) +{ + namespace logs_sdk = opentelemetry::sdk::logs; + namespace logs_exp = opentelemetry::exporter::logs; + + // Set up logger provider + auto exporter = logs_exp::OStreamLogRecordExporterFactory::Create(); + auto processor = logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(exporter)); + auto provider = logs_sdk::LoggerProviderFactory::Create(std::move(processor)); + auto provider_ptr = nostd::shared_ptr(provider.release()); + logs_api::Provider::SetLoggerProvider(provider_ptr); + // Save original stream buffer, then redirect cout to our new stream buffer + std::streambuf *original = std::cout.rdbuf(); + std::stringstream output; + std::cout.rdbuf(output.rdbuf()); + // Set up logging threads + const auto count = 100UL; + std::vector threads; + threads.reserve(count); + + const auto pre_log = std::chrono::system_clock::now().time_since_epoch().count(); + + for (size_t index = 0; index < count; ++index) + { + threads.emplace_back([this, index]() { LOG4CXX_INFO(logger_, "Test message " << index); }); + } + + for (auto &task : threads) + { + if (task.joinable()) + { + task.join(); + } + } + + const auto post_log = std::chrono::system_clock::now().time_since_epoch().count(); + + // Reset cout's original stringstream buffer + std::cout.rdbuf(original); + // Extract messages with timestamps + const auto field_name_length = 23UL; + std::vector messages; + std::vector timestamps; + std::string str; + + while (std::getline(output, str, '\n')) + { + if (str.find(" timestamp : ") != std::string::npos) + { + timestamps.push_back(std::strtoul(str.substr(field_name_length).c_str(), nullptr, 10)); + } + else if (str.find(" body : ") != std::string::npos) + { + messages.push_back(str.substr(field_name_length)); + } + } + + for (size_t index = 0; index < count; ++index) + { + const auto &message = "Test message " + std::to_string(index); + const auto &entry = std::find(messages.begin(), messages.end(), message); + ASSERT_TRUE(entry != messages.end()) << message; + + const auto offset = std::distance(messages.begin(), entry); + ASSERT_GE(timestamps[offset], pre_log); + ASSERT_LE(timestamps[offset], post_log); + } +} From 45be8c14247634ec27f99d2a185987ff52b128da Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Mon, 12 Feb 2024 11:56:13 -0800 Subject: [PATCH 15/25] Consistent naming of Geneva exporter with logs and trace for Windows and Linux (#380) --- exporters/fluentd/CMakeLists.txt | 42 ++++++++++++--------------- exporters/geneva-trace/CMakeLists.txt | 12 ++++++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/exporters/fluentd/CMakeLists.txt b/exporters/fluentd/CMakeLists.txt index 1c3fb66db..0cd7354b3 100644 --- a/exporters/fluentd/CMakeLists.txt +++ b/exporters/fluentd/CMakeLists.txt @@ -57,49 +57,49 @@ endif() include_directories(include) # create fluentd trace exporter -add_library(opentelemetry_exporter_fluentd_trace src/trace/fluentd_exporter.cc - src/trace/recordable.cc) +add_library(opentelemetry_exporter_geneva_trace src/trace/fluentd_exporter.cc + src/trace/recordable.cc) if(MAIN_PROJECT) - target_include_directories(opentelemetry_exporter_fluentd_trace + target_include_directories(opentelemetry_exporter_geneva_trace PRIVATE ${OPENTELEMETRY_CPP_INCLUDE_DIRS}) target_link_libraries( - opentelemetry_exporter_fluentd_trace + opentelemetry_exporter_geneva_trace PUBLIC ${OPENTELEMETRY_CPP_LIBRARIES} INTERFACE nlohmann_json::nlohmann_json) - set_target_properties(opentelemetry_exporter_fluentd_trace + set_target_properties(opentelemetry_exporter_geneva_trace PROPERTIES EXPORT_NAME trace) else() target_link_libraries( - opentelemetry_exporter_fluentd_trace + opentelemetry_exporter_geneva_trace PUBLIC opentelemetry_trace opentelemetry_resources opentelemetry_common INTERFACE nlohmann_json::nlohmann_json) endif() # create fluentd logs exporter -add_library(opentelemetry_exporter_fluentd_logs src/log/fluentd_exporter.cc - src/log/recordable.cc) +add_library(opentelemetry_exporter_geneva_logs src/log/fluentd_exporter.cc + src/log/recordable.cc) if(MAIN_PROJECT) - target_include_directories(opentelemetry_exporter_fluentd_logs + target_include_directories(opentelemetry_exporter_geneva_logs PRIVATE ${OPENTELEMETRY_CPP_INCLUDE_DIRS}) target_link_libraries( - opentelemetry_exporter_fluentd_logs + opentelemetry_exporter_geneva_logs PUBLIC ${OPENTELEMETRY_CPP_LIBRARIES} INTERFACE nlohmann_json::nlohmann_json) - set_target_properties(opentelemetry_exporter_fluentd_logs + set_target_properties(opentelemetry_exporter_geneva_logs PROPERTIES EXPORT_NAME logs) else() target_link_libraries( - opentelemetry_exporter_fluentd_logs + opentelemetry_exporter_geneva_logs PUBLIC opentelemetry_logs opentelemetry_resources opentelemetry_common INTERFACE nlohmann_json::nlohmann_json) endif() if(nlohmann_json_clone) - add_dependencies(opentelemetry_exporter_fluentd_trace + add_dependencies(opentelemetry_exporter_geneva_logs nlohmann_json::nlohmann_json) - add_dependencies(opentelemetry_exporter_fluentd_logs + add_dependencies(opentelemetry_exporter_geneva_logs nlohmann_json::nlohmann_json) include_directories(${PROJECT_BINARY_DIR}/include) endif() @@ -114,14 +114,8 @@ endif() if(OPENTELEMETRY_INSTALL) install( - TARGETS opentelemetry_exporter_fluentd_trace - EXPORT "${PROJECT_NAME}-target" - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - - install( - TARGETS opentelemetry_exporter_fluentd_logs + TARGETS opentelemetry_exporter_geneva_logs + opentelemetry_exporter_geneva_trace EXPORT "${PROJECT_NAME}-target" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -149,7 +143,7 @@ if(BUILD_TESTING) opentelemetry_common opentelemetry_trace opentelemetry_resources - opentelemetry_exporter_fluentd_trace) + opentelemetry_exporter_geneva_trace) if(nlohmann_json_clone) add_dependencies(fluentd_recordable_trace_test nlohmann_json::nlohmann_json) @@ -172,7 +166,7 @@ if(BUILD_TESTING) opentelemetry_common opentelemetry_logs opentelemetry_resources - opentelemetry_exporter_fluentd_logs) + opentelemetry_exporter_geneva_logs) if(nlohmann_json_clone) add_dependencies(fluentd_recordable_logs_test nlohmann_json::nlohmann_json) diff --git a/exporters/geneva-trace/CMakeLists.txt b/exporters/geneva-trace/CMakeLists.txt index 3baec459e..3196bb611 100644 --- a/exporters/geneva-trace/CMakeLists.txt +++ b/exporters/geneva-trace/CMakeLists.txt @@ -12,9 +12,15 @@ if(MAIN_PROJECT) option(WITH_EXAMPLES "Build examples" ON) endif() -add_library(opentelemetry-cpp-geneva-trace-log-exporter INTERFACE) +add_library(opentelemetry_exporter_geneva_trace INTERFACE) target_include_directories( - opentelemetry-cpp-geneva-trace-log-exporter INTERFACE + opentelemetry_exporter_geneva_trace INTERFACE + $ + $) + +add_library(opentelemetry_exporter_geneva_logs INTERFACE) +target_include_directories( + opentelemetry_exporter_geneva_logs INTERFACE $ $) @@ -27,7 +33,7 @@ if(OPENTELEMETRY_INSTALL) install(DIRECTORY include/ DESTINATION include) install( - TARGETS opentelemetry-cpp-geneva-trace-log-exporter + TARGETS opentelemetry_exporter_geneva_trace opentelemetry_exporter_geneva_logs EXPORT "${PROJECT_NAME}-target") if(NOT MAIN_PROJECT) From 2cf5c8f7d470e41a59783f7cccf83a8f1237127d Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Mon, 12 Feb 2024 23:32:50 +0100 Subject: [PATCH 16/25] [DOC] Fix calendar link (#381) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 248a3570e..fe53964f1 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) We meet weekly, and the time of the meeting alternates between Monday at 13:00 PT and Wednesday at 9:00 PT. The meeting is subject to change depending on contributors' availability. Check the [OpenTelemetry community -calendar](https://calendar.google.com/calendar/embed?src=google.com_b79e3e90j7bbsa2n2p5an5lf60%40group.calendar.google.com) +calendar](https://github.com/open-telemetry/community#calendar) for specific dates and Zoom meeting links. Meeting notes are available as a public [Google From 10ca06b3f7e5b1a7da262040d6d5dc4cfcf7048f Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Mon, 12 Feb 2024 15:46:49 -0800 Subject: [PATCH 17/25] Add Tom Tan as maintainer (#382) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe53964f1..58e93debb 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ For edit access, get in touch on * [Lalit Kumar Bhasin](https://github.com/lalitb), Microsoft * [Marc Alff](https://github.com/marcalff), Oracle +* [Tom Tan](https://github.com/ThomsonTan), Microsoft [Approvers](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver) ([@open-telemetry/cpp-contrib-approvers](https://github.com/orgs/open-telemetry/teams/cpp-contrib-approvers)): @@ -37,7 +38,6 @@ For edit access, get in touch on * [Siim Kallas](https://github.com/seemk), Splunk * [Tobias Stadler](https://github.com/tobiasstadler) * [Tomasz Rojek](https://github.com/TomRoSystems) -* [Tom Tan](https://github.com/ThomsonTan), Microsoft [Emeritus Maintainer/Approver/Triager](https://github.com/open-telemetry/community/blob/main/community-membership.md#emeritus-maintainerapprovertriager): From 437cd26256b0c5f0c6129fde7a25541a8cacbbac Mon Sep 17 00:00:00 2001 From: Justin McCann <1016935+justin-mccann@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:53:09 -0500 Subject: [PATCH 18/25] Remove v1 namespace usage to permit use of ABI v2 (#383) --- .../opentelemetry/exporters/fluentd/common/fluentd_common.h | 4 ++-- exporters/user_events/src/metrics_exporter.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exporters/fluentd/include/opentelemetry/exporters/fluentd/common/fluentd_common.h b/exporters/fluentd/include/opentelemetry/exporters/fluentd/common/fluentd_common.h index a5ec1c8e1..a017fac61 100644 --- a/exporters/fluentd/include/opentelemetry/exporters/fluentd/common/fluentd_common.h +++ b/exporters/fluentd/include/opentelemetry/exporters/fluentd/common/fluentd_common.h @@ -116,8 +116,8 @@ inline std::string AttributeValueToString( result = std::to_string(nostd::get(value)); } else if (nostd::holds_alternative(value)) { result = std::string(nostd::get(value)); - } else if (nostd::holds_alternative(value)) { - result = std::string(nostd::get(value).data()); + } else if (nostd::holds_alternative(value)) { + result = std::string(nostd::get(value).data()); } else { LOG_WARN("[Fluentd Exporter] AttributeValueToString - " " Nested attributes not supported - ignored"); diff --git a/exporters/user_events/src/metrics_exporter.cc b/exporters/user_events/src/metrics_exporter.cc index 8fa1fcc8f..45424d3d6 100644 --- a/exporters/user_events/src/metrics_exporter.cc +++ b/exporters/user_events/src/metrics_exporter.cc @@ -65,7 +65,7 @@ sdk_common::ExportResult Exporter::Export( return sdk_common::ExportResult::kSuccess; } - proto::collector::metrics::v1::ExportMetricsServiceRequest request; + proto::collector::metrics::ExportMetricsServiceRequest request; otlp_exporter::OtlpMetricUtils::PopulateRequest(data, &request); int size = (int)request.ByteSizeLong(); From fafac0ae1c9bb8f61eb4c6e6e6be5cf6f62c3078 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Fri, 16 Feb 2024 10:06:33 -0800 Subject: [PATCH 19/25] [geneva-exporter] Update main repo to pick up the namespace clash fix (#384) --- exporters/geneva-trace/third_party/opentelemetry-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/geneva-trace/third_party/opentelemetry-cpp b/exporters/geneva-trace/third_party/opentelemetry-cpp index d32f9609b..c7a88c479 160000 --- a/exporters/geneva-trace/third_party/opentelemetry-cpp +++ b/exporters/geneva-trace/third_party/opentelemetry-cpp @@ -1 +1 @@ -Subproject commit d32f9609b965dbb34030c1740ddb464d93cd3065 +Subproject commit c7a88c479fba3c7ee039e426ba6085b344a8759a From 3f1b0ef547a304635aa1357af5990b4291c9f074 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Sat, 17 Feb 2024 13:47:46 -0800 Subject: [PATCH 20/25] [geneva-exporter] add traceflag override method to fluentd exporter (#385) --- .../opentelemetry/exporters/fluentd/trace/recordable.h | 2 ++ exporters/fluentd/src/trace/fluentd_exporter.cc | 2 +- exporters/fluentd/src/trace/recordable.cc | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/exporters/fluentd/include/opentelemetry/exporters/fluentd/trace/recordable.h b/exporters/fluentd/include/opentelemetry/exporters/fluentd/trace/recordable.h index 4b3fb8294..378f7bcba 100644 --- a/exporters/fluentd/include/opentelemetry/exporters/fluentd/trace/recordable.h +++ b/exporters/fluentd/include/opentelemetry/exporters/fluentd/trace/recordable.h @@ -71,6 +71,8 @@ class Recordable final : public sdk::trace::Recordable { const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope) noexcept override; + void SetTraceFlags(opentelemetry::trace::TraceFlags flags) noexcept override; + private: std::string tag_; nlohmann::json events_; diff --git a/exporters/fluentd/src/trace/fluentd_exporter.cc b/exporters/fluentd/src/trace/fluentd_exporter.cc index 485f490da..784294b91 100644 --- a/exporters/fluentd/src/trace/fluentd_exporter.cc +++ b/exporters/fluentd/src/trace/fluentd_exporter.cc @@ -60,7 +60,7 @@ FluentdExporter::FluentdExporter() */ std::unique_ptr FluentdExporter::MakeRecordable() noexcept { - return std::unique_ptr(new Recordable); + return std::unique_ptr(new opentelemetry::exporter::fluentd::trace::Recordable()); } /** diff --git a/exporters/fluentd/src/trace/recordable.cc b/exporters/fluentd/src/trace/recordable.cc index 010c02d4e..77527a8b3 100644 --- a/exporters/fluentd/src/trace/recordable.cc +++ b/exporters/fluentd/src/trace/recordable.cc @@ -148,6 +148,10 @@ void Recordable::SetInstrumentationScope( instrumentation_scope.GetVersion(); } +void Recordable::SetTraceFlags(opentelemetry::trace::TraceFlags flags) noexcept { + // TODO: process trace flags +} + } // namespace trace } // namespace fluentd } // namespace exporter From 15483a6e77f246dadb85e67fe78cdb5c4003d2f1 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Thu, 22 Feb 2024 15:01:15 -0800 Subject: [PATCH 21/25] [Geneva] remove HAVE_CONSOLE_LOG which should defined by the user (#386) --- exporters/fluentd/src/log/fluentd_exporter.cc | 3 --- exporters/fluentd/src/trace/fluentd_exporter.cc | 1 - 2 files changed, 4 deletions(-) diff --git a/exporters/fluentd/src/log/fluentd_exporter.cc b/exporters/fluentd/src/log/fluentd_exporter.cc index 569c82b57..7efb106d5 100644 --- a/exporters/fluentd/src/log/fluentd_exporter.cc +++ b/exporters/fluentd/src/log/fluentd_exporter.cc @@ -1,8 +1,5 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#ifndef HAVE_CONSOLE_LOG -#define HAVE_CONSOLE_LOG -#endif #include "opentelemetry/exporters/fluentd/log/fluentd_exporter.h" #include "opentelemetry/exporters/fluentd/log/recordable.h" diff --git a/exporters/fluentd/src/trace/fluentd_exporter.cc b/exporters/fluentd/src/trace/fluentd_exporter.cc index 784294b91..35950d33d 100644 --- a/exporters/fluentd/src/trace/fluentd_exporter.cc +++ b/exporters/fluentd/src/trace/fluentd_exporter.cc @@ -1,6 +1,5 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#define HAVE_CONSOLE_LOG #include "opentelemetry/exporters/fluentd/trace/fluentd_exporter.h" #include "opentelemetry/exporters/fluentd/trace/recordable.h" From 68885bee3b160ee7a032872c8fa6ecf68a7f4edc Mon Sep 17 00:00:00 2001 From: Ehsan Saei <71217171+esigo@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:54:28 +0100 Subject: [PATCH 22/25] Add Ehsan as maintainer (#387) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58e93debb..5dbd427e2 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ For edit access, get in touch on [Maintainers](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer) ([@open-telemetry/cpp-contrib-maintainers](https://github.com/orgs/open-telemetry/teams/cpp-contrib-maintainers)): +* [Ehsan Saei](https://github.com/esigo) * [Lalit Kumar Bhasin](https://github.com/lalitb), Microsoft * [Marc Alff](https://github.com/marcalff), Oracle * [Tom Tan](https://github.com/ThomsonTan), Microsoft @@ -30,7 +31,6 @@ For edit access, get in touch on ([@open-telemetry/cpp-contrib-approvers](https://github.com/orgs/open-telemetry/teams/cpp-contrib-approvers)): * [DEBAJIT DAS](https://github.com/DebajitDas), Cisco -* [Ehsan Saei](https://github.com/esigo) * [Johannes Tax](https://github.com/pyohannes), Grafana Labs * [Josh Suereth](https://github.com/jsuereth), Google * [Kumar Pratyush](https://github.com/kpratyus), Cisco From 60bed2a84435fc8febf41173a6133f242fb26d6e Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Fri, 8 Mar 2024 15:56:00 -0800 Subject: [PATCH 23/25] [Geneva] update Log class name in geneva_logger_exporter.h (#388) --- .../exporters/geneva/geneva_logger_exporter.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_logger_exporter.h b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_logger_exporter.h index d5a05db8c..8121703c6 100644 --- a/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_logger_exporter.h +++ b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_logger_exporter.h @@ -3,12 +3,11 @@ #pragma once -#define ENABLE_LOGS_PREVIEW #include "geneva_exporter_options.h" #include #include +#include #include -#include #include OPENTELEMETRY_BEGIN_NAMESPACE @@ -27,10 +26,10 @@ static inline bool InitializeGenevaExporter( const GenevaExporterOptions options opentelemetry::exporter::fluentd::common::FluentdExporterOptions fluentd_options; fluentd_options.retry_count = options.retry_count; fluentd_options.endpoint = options.socket_endpoint; - auto exporter = std::unique_ptr( + auto exporter = std::unique_ptr( new opentelemetry::exporter::fluentd::logs::FluentdExporter(fluentd_options)); - auto processor = std::unique_ptr( - new opentelemetry::sdk::logs::BatchLogProcessor(std::move(exporter), options.max_queue_size, options.schedule_delay_millis, options.max_export_batch_size)); + auto processor = std::unique_ptr( + new opentelemetry::sdk::logs::BatchLogRecordProcessor(std::move(exporter), options.max_queue_size, options.schedule_delay_millis, options.max_export_batch_size)); auto provider = std::shared_ptr( new opentelemetry::sdk::logs::LoggerProvider(std::move(processor))); // Set the global logger provider From d2e6acbf1d20497b9ad27b4e0e428eea11448176 Mon Sep 17 00:00:00 2001 From: Tom Tan Date: Sun, 10 Mar 2024 20:54:43 -0700 Subject: [PATCH 24/25] [Geneva] add missing exporter option header (#389) --- .../opentelemetry/exporters/geneva/geneva_tracer_exporter.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_tracer_exporter.h b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_tracer_exporter.h index bbf1c684f..c52e14186 100644 --- a/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_tracer_exporter.h +++ b/exporters/fluentd/include/opentelemetry/exporters/geneva/geneva_tracer_exporter.h @@ -7,8 +7,9 @@ #include "geneva_exporter_options.h" #include #include -#include #include +#include +#include #include OPENTELEMETRY_BEGIN_NAMESPACE From 8bf5ebe5f6954d3da35407352ad87db2e0d8772f Mon Sep 17 00:00:00 2001 From: Aryan Ishan <54237311+aryanishan1001@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:01:23 +0530 Subject: [PATCH 25/25] Adding functionality to add custom attributes in nginx otel-webserver-module. (#394) * Nginx attributes feature implementation * corrected branch --- .../otel-webserver-module/Dockerfile | 4 +- .../otel-webserver-module/codeql-env.sh | 2 +- .../include/core/api/Payload.h | 2 + .../include/core/api/opentelemetry_ngx_api.h | 3 + .../include/core/sdkwrapper/SdkConstants.h | 1 + .../src/core/api/RequestProcessingEngine.cpp | 6 + .../src/core/api/opentelemetry_ngx_api.cpp | 8 +- .../src/nginx/ngx_http_opentelemetry_module.c | 125 +++++++++++++++++- .../src/nginx/ngx_http_opentelemetry_module.h | 3 + 9 files changed, 146 insertions(+), 8 deletions(-) diff --git a/instrumentation/otel-webserver-module/Dockerfile b/instrumentation/otel-webserver-module/Dockerfile index d83f84f48..79a6860b5 100644 --- a/instrumentation/otel-webserver-module/Dockerfile +++ b/instrumentation/otel-webserver-module/Dockerfile @@ -24,7 +24,7 @@ ARG LOG4CXX_VERSION="0.11.0" ARG GTEST_VERSION="1.10.0" ARG AUTOMAKE_VERSION="1.16.3" ARG PCRE_VERSION="8.44" -ARG NGINX_VERSION="1.18.0" +ARG NGINX_VERSION="1.24.0" # create default non-root user @@ -273,7 +273,7 @@ RUN cd /otel-webserver-module/build \ && cd / RUN cp /otel-webserver-module/conf/nginx/opentelemetry_module.conf /opt/ \ - && sed -i '8i load_module /opt/opentelemetry-webserver-sdk/WebServerModule/Nginx/ngx_http_opentelemetry_module.so;' /etc/nginx/nginx.conf \ + && sed -i '8i load_module /opt/opentelemetry-webserver-sdk/WebServerModule/Nginx/1.24.0/ngx_http_opentelemetry_module.so;' /etc/nginx/nginx.conf \ && sed -i '33i include /opt/opentelemetry_module.conf;' /etc/nginx/nginx.conf \ && cd / diff --git a/instrumentation/otel-webserver-module/codeql-env.sh b/instrumentation/otel-webserver-module/codeql-env.sh index 711f349fb..5a8383dce 100755 --- a/instrumentation/otel-webserver-module/codeql-env.sh +++ b/instrumentation/otel-webserver-module/codeql-env.sh @@ -36,7 +36,7 @@ APRUTIL_VERSION="1.6.1" LOG4CXX_VERSION="0.11.0" GTEST_VERSION="1.10.0" PCRE_VERSION="8.44" -NGINX_VERSION="1.18.0" +NGINX_VERSION="1.24.0" # Install GRPC git clone --shallow-submodules --depth 1 --recurse-submodules -b v${GRPC_VERSION} \ diff --git a/instrumentation/otel-webserver-module/include/core/api/Payload.h b/instrumentation/otel-webserver-module/include/core/api/Payload.h index e1cf2fe37..bc5a34ebb 100644 --- a/instrumentation/otel-webserver-module/include/core/api/Payload.h +++ b/instrumentation/otel-webserver-module/include/core/api/Payload.h @@ -87,11 +87,13 @@ class RequestPayload std::unordered_map& get_request_headers() { return request_headers; } + }; struct ResponsePayload { std::unordered_map response_headers; + std::unordered_map otel_attributes; unsigned int status_code{sdkwrapper::kStatusCodeInit}; }; diff --git a/instrumentation/otel-webserver-module/include/core/api/opentelemetry_ngx_api.h b/instrumentation/otel-webserver-module/include/core/api/opentelemetry_ngx_api.h index 7a47ec41b..fdcdc43e0 100644 --- a/instrumentation/otel-webserver-module/include/core/api/opentelemetry_ngx_api.h +++ b/instrumentation/otel-webserver-module/include/core/api/opentelemetry_ngx_api.h @@ -45,6 +45,7 @@ typedef struct { const char* http_post_param; const char* request_method; const char* client_ip; + http_headers* propagation_headers; http_headers* request_headers; @@ -55,6 +56,8 @@ typedef struct { typedef struct { http_headers* response_headers; int response_headers_count; + http_headers* otel_attributes; + int otel_attributes_count; unsigned int status_code; }response_payload; diff --git a/instrumentation/otel-webserver-module/include/core/sdkwrapper/SdkConstants.h b/instrumentation/otel-webserver-module/include/core/sdkwrapper/SdkConstants.h index bd8092939..12d3188f8 100644 --- a/instrumentation/otel-webserver-module/include/core/sdkwrapper/SdkConstants.h +++ b/instrumentation/otel-webserver-module/include/core/sdkwrapper/SdkConstants.h @@ -39,6 +39,7 @@ const std::string kAttrNETHostPort = "net.host.port"; const std::string kAttrRequestProtocol = "request_protocol"; const std::string kHTTPFlavor1_0 = "1.0"; const std::string kHTTPFlavor1_1 = "1.1"; +const std::string kOtelAttributes = "otel.attribute."; constexpr int HTTP_ERROR_1XX = 100; diff --git a/instrumentation/otel-webserver-module/src/core/api/RequestProcessingEngine.cpp b/instrumentation/otel-webserver-module/src/core/api/RequestProcessingEngine.cpp index c246e5a49..e24d9f52e 100644 --- a/instrumentation/otel-webserver-module/src/core/api/RequestProcessingEngine.cpp +++ b/instrumentation/otel-webserver-module/src/core/api/RequestProcessingEngine.cpp @@ -82,6 +82,7 @@ OTEL_SDK_STATUS_CODE RequestProcessingEngine::startRequest( std::string(itr->first); keyValueMap[key] = itr->second; } + auto span = m_sdkWrapper->CreateSpan(spanName, sdkwrapper::SpanKind::SERVER, keyValueMap, payload->get_http_headers()); LOG4CXX_TRACE(mLogger, "Span started for context: [" << wscontext @@ -157,6 +158,11 @@ OTEL_SDK_STATUS_CODE RequestProcessingEngine::endRequest( rootSpan->AddAttribute(key, itr->second); } + for (auto itr = payload->otel_attributes.begin(); itr != payload->otel_attributes.end(); itr++) { + std::string key = std::string(kOtelAttributes) + std::string(itr->first); + rootSpan->AddAttribute(key, itr->second); + } + rootSpan->AddAttribute(kAttrHTTPStatusCode, payload->status_code); } diff --git a/instrumentation/otel-webserver-module/src/core/api/opentelemetry_ngx_api.cpp b/instrumentation/otel-webserver-module/src/core/api/opentelemetry_ngx_api.cpp index 872a10045..37a8be8c8 100644 --- a/instrumentation/otel-webserver-module/src/core/api/opentelemetry_ngx_api.cpp +++ b/instrumentation/otel-webserver-module/src/core/api/opentelemetry_ngx_api.cpp @@ -119,13 +119,17 @@ OTEL_SDK_STATUS_CODE endRequest(OTEL_SDK_HANDLE_REQ req_handle_key, const char* if (payload != NULL) { for (int i = 0; i < payload->response_headers_count; i++) { std::string key(payload->response_headers[i].name); - if (responseHeadersToCapture.find(key) - != responseHeadersToCapture.end()) { + if (responseHeadersToCapture.find(key) != responseHeadersToCapture.end()) { responsePayload->response_headers[key] = payload->response_headers[i].value; } } responsePayload->status_code = payload->status_code; + + for(int i=0; i < payload->otel_attributes_count; i++){ + std::string key(payload->otel_attributes[i].name); + responsePayload->otel_attributes[key] = payload->otel_attributes[i].value; + } } res = wsAgent.endRequest(req_handle_key, errMsg, responsePayload.get()); diff --git a/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.c b/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.c index 8e4d5005b..ce98b5deb 100644 --- a/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.c +++ b/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.c @@ -383,10 +383,19 @@ static ngx_command_t ngx_http_opentelemetry_commands[] = { NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_opentelemetry_loc_conf_t, nginxTrustIncomingSpans), NULL}, - - ngx_null_command /* command termination */ + + { ngx_string("NginxAttributes"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_otel_attributes_set, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL}, + + /* command termination */ + ngx_null_command }; + /* The module context. */ static ngx_http_module_t ngx_http_opentelemetry_module_ctx = { NULL, /* preconfiguration */ @@ -454,6 +463,7 @@ static char* ngx_http_opentelemetry_merge_loc_conf(ngx_conf_t *cf, void *parent, ngx_http_opentelemetry_loc_conf_t *prev = parent; ngx_http_opentelemetry_loc_conf_t *conf = child; ngx_otel_set_global_context(prev); + ngx_otel_set_attributes(prev, conf); ngx_conf_merge_value(conf->nginxModuleEnabled, prev->nginxModuleEnabled, 1); ngx_conf_merge_value(conf->nginxModuleReportAllInstrumentedModules, prev->nginxModuleReportAllInstrumentedModules, 0); @@ -673,6 +683,34 @@ static void ngx_http_opentelemetry_exit_worker(ngx_cycle_t *cycle) ngx_log_error(NGX_LOG_ERR, cycle->log, 0, "mod_opentelemetry: ngx_http_opentelemetry_exit_worker: Exiting Nginx Worker for process with PID: %s**********", worker_conf->pid); } } +static char* ngx_otel_attributes_set(ngx_conf_t* cf, ngx_command_t* cmd, void* conf) { + ngx_http_opentelemetry_loc_conf_t * my_conf=(ngx_http_opentelemetry_loc_conf_t *)conf; + + ngx_str_t *value = cf->args->elts; + + ngx_array_t *arr; + ngx_str_t *elt; + ngx_int_t arg_count = cf->args->nelts; + + arr = ngx_array_create(cf->pool, arg_count, sizeof(ngx_str_t)); + + if (arr == NULL) { + return NGX_CONF_ERROR; + } + + // Add elements to the array + for (ngx_int_t i = 1; i < arg_count; i++) { + elt = ngx_array_push(arr); + if (elt == NULL) { + return NGX_CONF_ERROR; + } + ngx_str_set(elt, value[i].data); + elt->len = ngx_strlen(value[i].data); + } + my_conf->nginxAttributes = arr; + return NGX_CONF_OK; + +} static char* ngx_otel_context_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ ngx_str_t* value; @@ -704,6 +742,20 @@ static void ngx_otel_set_global_context(ngx_http_opentelemetry_loc_conf_t * prev } } +static void ngx_otel_set_attributes(ngx_http_opentelemetry_loc_conf_t * prev, ngx_http_opentelemetry_loc_conf_t * conf) +{ + if (conf->nginxAttributes && (conf->nginxAttributes->nelts) > 0) { + return; + } + if (prev->nginxAttributes && (prev->nginxAttributes->nelts) > 0) { + if((conf->nginxAttributes) == NULL) + { + conf->nginxAttributes = prev->nginxAttributes; + } + } + return; +} + /* Begin a new interaction */ @@ -737,6 +789,7 @@ static OTEL_SDK_STATUS_CODE otel_startInteraction(ngx_http_request_t* r, const c int ix = 0; res = startModuleInteraction((void*)ctx->otel_req_handle_key, module_name, "", resolveBackends, propagationHeaders, &ix); + if (OTEL_ISSUCCESS(res)) { removeUnwantedHeader(r); @@ -892,6 +945,39 @@ static ngx_uint_t otel_getErrorCode(ngx_http_request_t* r) else return 0; } +static void resolve_attributes_variables(ngx_http_request_t* r) +{ + ngx_http_opentelemetry_loc_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_opentelemetry_module); + + for (ngx_uint_t j = 0; j < conf->nginxAttributes->nelts; j++) { + + void *element = conf->nginxAttributes->elts + (j * conf->nginxAttributes->size); + ngx_str_t var_name = (((ngx_str_t *)(conf->nginxAttributes->elts))[j]); + ngx_uint_t key; // The variable's hashed key. + ngx_http_variable_value_t *value; // Pointer to the value object. + + if(var_name.data[0] == '$'){ + // Get the hashed key. + ngx_str_t new_var_name = var_name; + new_var_name.data++; + new_var_name.len--; + key = ngx_hash_key(new_var_name.data, new_var_name.len); + + // Get the variable. + value = ngx_http_get_variable(r, &new_var_name, key); + + if (value == NULL || value->not_found) { + // Variable was not found. + } else { + ngx_str_t * ngx_str = (ngx_str_t *)(element); + ngx_str->data = value->data; + ngx_str->len = ngx_strlen(value); + // Variable was found, `value` now contains the value. + } + } + } +} + static ngx_flag_t ngx_initialize_opentelemetry(ngx_http_request_t *r) { // check to see if we have already been initialized @@ -1274,7 +1360,7 @@ static ngx_int_t ngx_http_otel_realip_handler(ngx_http_request_t *r){ otel_startInteraction(r, "ngx_http_realip_module"); ngx_int_t rvalue = h[NGX_HTTP_REALIP_MODULE_INDEX](r); - otel_stopInteraction(r, "ngx_http_realip_module", OTEL_SDK_NO_HANDLE); + otel_stopInteraction(r, "ngx_http_realip_module", OTEL_SDK_NO_HANDLE); return rvalue; } @@ -1555,6 +1641,7 @@ static void removeUnwantedHeader(ngx_http_request_t* r) } } + static void fillRequestPayload(request_payload* req_payload, ngx_http_request_t* r){ ngx_list_part_t *part; ngx_table_elt_t *header; @@ -1636,6 +1723,7 @@ static void fillRequestPayload(request_payload* req_payload, ngx_http_request_t* ngx_http_opentelemetry_loc_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_opentelemetry_module); + part = &r->headers_in.headers.part; header = (ngx_table_elt_t*)part->elts; nelts = part->nelts; @@ -1669,6 +1757,7 @@ static void fillRequestPayload(request_payload* req_payload, ngx_http_request_t* } req_payload->propagation_count = propagation_headers_idx; req_payload->request_headers_count = request_headers_idx; + } static void fillResponsePayload(response_payload* res_payload, ngx_http_request_t* r) @@ -1689,6 +1778,36 @@ static void fillResponsePayload(response_payload* res_payload, ngx_http_request_ res_payload->response_headers = ngx_pcalloc(r->pool, nelts * sizeof(http_headers)); ngx_uint_t headers_count = 0; + ngx_http_opentelemetry_loc_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_opentelemetry_module); + + if (conf->nginxAttributes && (conf->nginxAttributes->nelts) > 0) { + + res_payload->otel_attributes = ngx_pcalloc(r->pool, ((nelts+1)/3) * sizeof(http_headers)); + int otel_attributes_idx=0; + + resolve_attributes_variables(r); + for (ngx_uint_t j = 0, isKey = 1, isValue = 0; j < conf->nginxAttributes->nelts; j++) { + const char* data = (const char*)(((ngx_str_t *)(conf->nginxAttributes->elts))[j]).data; + if(strcmp(data, ",") == 0){ + otel_attributes_idx++; + continue; + } + else if(isKey){ + res_payload->otel_attributes[otel_attributes_idx].name = data; + } + else{ + res_payload->otel_attributes[otel_attributes_idx].value = data; + } + isKey=!isKey; + isValue=!isValue; + } + res_payload->otel_attributes_count = otel_attributes_idx+1; + }else { + res_payload->otel_attributes_count = 0; + } + + + for (ngx_uint_t j = 0; j < nelts; j++) { h = &header[j]; diff --git a/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.h b/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.h index 6043b5b78..7253f130e 100644 --- a/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.h +++ b/instrumentation/otel-webserver-module/src/nginx/ngx_http_opentelemetry_module.h @@ -104,6 +104,7 @@ typedef struct { ngx_str_t nginxModuleResponseHeaders; ngx_str_t nginxModuleOtelExporterOtlpHeaders; ngx_flag_t nginxTrustIncomingSpans; + ngx_array_t *nginxAttributes; } ngx_http_opentelemetry_loc_conf_t; @@ -154,7 +155,9 @@ static void otel_payload_decorator(ngx_http_request_t* r, OTEL_SDK_ENV_RECORD* p static ngx_flag_t otel_requestHasErrors(ngx_http_request_t* r); static ngx_uint_t otel_getErrorCode(ngx_http_request_t* r); static char* ngx_otel_context_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char* ngx_otel_attributes_set(ngx_conf_t* cf, ngx_command_t*, void* conf); static void ngx_otel_set_global_context(ngx_http_opentelemetry_loc_conf_t * prev); +static void ngx_otel_set_attributes(ngx_http_opentelemetry_loc_conf_t * prev, ngx_http_opentelemetry_loc_conf_t * conf); static void removeUnwantedHeader(ngx_http_request_t* r); /* Module specific handler