From c48e25e75b3740527e5865f5bf7cb3c4ecea54d2 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Mon, 8 Apr 2024 14:56:05 -0400 Subject: [PATCH 01/18] initial submission of logs bridge api and sdk --- CMakeLists.txt | 26 +- OtelMatlabProxyFactory.cpp | 15 +- api/context/+opentelemetry/+context/Context.m | 4 +- api/logs/+opentelemetry/+logs/Logger.m | 257 +++++++++++++ .../+opentelemetry/+logs/LoggerProvider.m | 70 ++++ api/logs/+opentelemetry/+logs/Provider.m | 28 ++ api/logs/+opentelemetry/+logs/getLogger.m | 17 + .../logs/LoggerProviderProxy.h | 47 +++ .../opentelemetry-matlab/logs/LoggerProxy.h | 26 ++ api/logs/src/LoggerProviderProxy.cpp | 39 ++ api/logs/src/LoggerProxy.cpp | 72 ++++ .../+otlp/OtlpGrpcLogRecordExporter.m | 99 +++++ .../+otlp/OtlpHttpLogRecordExporter.m | 98 +++++ .../+otlp/defaultLogRecordExporter.m | 16 + .../otlp/OtlpGrpcLogRecordExporterProxy.h | 42 +++ .../otlp/OtlpHttpLogRecordExporterProxy.h | 47 +++ .../src/OtlpGrpcLogRecordExporterProxy.cpp | 72 ++++ .../src/OtlpHttpLogRecordExporterProxy.cpp | 77 ++++ .../+opentelemetry/+sdk/+common/Cleanup.m | 34 +- .../+sdk/+logs/BatchLogRecordProcessor.m | 89 +++++ .../+sdk/+logs/LogRecordExporter.m | 19 + .../+sdk/+logs/LogRecordProcessor.m | 26 ++ .../+sdk/+logs/LoggerProvider.m | 142 +++++++ .../+sdk/+logs/SimpleLogRecordProcessor.m | 30 ++ .../sdk/logs/BatchLogRecordProcessorProxy.h | 33 ++ .../sdk/logs/LogRecordExporterProxy.h | 16 + .../sdk/logs/LogRecordProcessorProxy.h | 24 ++ .../sdk/logs/LoggerProviderProxy.h | 27 ++ .../sdk/logs/SimpleLogRecordProcessorProxy.h | 26 ++ sdk/logs/src/BatchLogRecordProcessorProxy.cpp | 53 +++ sdk/logs/src/LoggerProviderProxy.cpp | 82 ++++ .../src/SimpleLogRecordProcessorProxy.cpp | 14 + test/tlogs.m | 352 ++++++++++++++++++ test/tlogs_sdk.m | 187 ++++++++++ 34 files changed, 2191 insertions(+), 15 deletions(-) create mode 100644 api/logs/+opentelemetry/+logs/Logger.m create mode 100644 api/logs/+opentelemetry/+logs/LoggerProvider.m create mode 100644 api/logs/+opentelemetry/+logs/Provider.m create mode 100644 api/logs/+opentelemetry/+logs/getLogger.m create mode 100644 api/logs/include/opentelemetry-matlab/logs/LoggerProviderProxy.h create mode 100644 api/logs/include/opentelemetry-matlab/logs/LoggerProxy.h create mode 100644 api/logs/src/LoggerProviderProxy.cpp create mode 100644 api/logs/src/LoggerProxy.cpp create mode 100644 exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcLogRecordExporter.m create mode 100644 exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m create mode 100644 exporters/otlp/+opentelemetry/+exporters/+otlp/defaultLogRecordExporter.m create mode 100644 exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcLogRecordExporterProxy.h create mode 100644 exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpLogRecordExporterProxy.h create mode 100644 exporters/otlp/src/OtlpGrpcLogRecordExporterProxy.cpp create mode 100644 exporters/otlp/src/OtlpHttpLogRecordExporterProxy.cpp create mode 100644 sdk/logs/+opentelemetry/+sdk/+logs/BatchLogRecordProcessor.m create mode 100644 sdk/logs/+opentelemetry/+sdk/+logs/LogRecordExporter.m create mode 100644 sdk/logs/+opentelemetry/+sdk/+logs/LogRecordProcessor.m create mode 100644 sdk/logs/+opentelemetry/+sdk/+logs/LoggerProvider.m create mode 100644 sdk/logs/+opentelemetry/+sdk/+logs/SimpleLogRecordProcessor.m create mode 100644 sdk/logs/include/opentelemetry-matlab/sdk/logs/BatchLogRecordProcessorProxy.h create mode 100644 sdk/logs/include/opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h create mode 100644 sdk/logs/include/opentelemetry-matlab/sdk/logs/LogRecordProcessorProxy.h create mode 100644 sdk/logs/include/opentelemetry-matlab/sdk/logs/LoggerProviderProxy.h create mode 100644 sdk/logs/include/opentelemetry-matlab/sdk/logs/SimpleLogRecordProcessorProxy.h create mode 100644 sdk/logs/src/BatchLogRecordProcessorProxy.cpp create mode 100644 sdk/logs/src/LoggerProviderProxy.cpp create mode 100644 sdk/logs/src/SimpleLogRecordProcessorProxy.cpp create mode 100644 test/tlogs.m create mode 100644 test/tlogs_sdk.m diff --git a/CMakeLists.txt b/CMakeLists.txt index d2379f1..d0da7ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,12 +155,14 @@ if(WITH_OTLP_HTTP) set(OTEL_CPP_LIBRARIES ${OTEL_CPP_LIBRARIES} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_http${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_http_client${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_http_metric${CMAKE_STATIC_LIBRARY_SUFFIX} + ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_http_log${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_http_client_curl${CMAKE_STATIC_LIBRARY_SUFFIX}) endif() if(WITH_OTLP_GRPC) set(OTEL_CPP_LIBRARIES ${OTEL_CPP_LIBRARIES} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_grpc${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_grpc_client${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_grpc_metrics${CMAKE_STATIC_LIBRARY_SUFFIX} + ${OTEL_CPP_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}opentelemetry_exporter_otlp_grpc_log${CMAKE_STATIC_LIBRARY_SUFFIX} ${OTEL_CPP_PREFIX}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}opentelemetry_proto_grpc${OTEL_PROTO_LIBRARY_SUFFIX}) endif() @@ -225,24 +227,28 @@ endif() set(TRACE_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/trace/include) set(METRICS_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/metrics/include) +set(LOGS_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/logs/include) set(CONTEXT_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/context/include) set(BAGGAGE_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/baggage/include) set(COMMON_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/common/include) set(TRACE_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/trace/include) set(METRICS_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/metrics/include) +set(LOGS_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/logs/include) set(COMMON_SDK_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/common/include) set(OTLP_EXPORTER_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/include) -set(OPENTELEMETRY_PROXY_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${TRACE_API_INCLUDE_DIR} ${METRICS_API_INCLUDE_DIR} ${CONTEXT_API_INCLUDE_DIR} ${BAGGAGE_API_INCLUDE_DIR} ${COMMON_API_INCLUDE_DIR} ${TRACE_SDK_INCLUDE_DIR} ${METRICS_SDK_INCLUDE_DIR} ${COMMON_SDK_INCLUDE_DIR} ${OTLP_EXPORTER_INCLUDE_DIR} ${OTEL_CPP_PREFIX}/include ${Matlab_INCLUDE_DIRS}) +set(OPENTELEMETRY_PROXY_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${TRACE_API_INCLUDE_DIR} ${METRICS_API_INCLUDE_DIR} ${LOGS_API_INCLUDE_DIR} ${CONTEXT_API_INCLUDE_DIR} ${BAGGAGE_API_INCLUDE_DIR} ${COMMON_API_INCLUDE_DIR} ${TRACE_SDK_INCLUDE_DIR} ${METRICS_SDK_INCLUDE_DIR} ${LOGS_SDK_INCLUDE_DIR} ${COMMON_SDK_INCLUDE_DIR} ${OTLP_EXPORTER_INCLUDE_DIR} ${OTEL_CPP_PREFIX}/include ${Matlab_INCLUDE_DIRS}) set(OPENTELEMETRY_PROXY_FACTORY_CLASS_NAME OtelMatlabProxyFactory) set(OPENTELEMETRY_PROXY_FACTORY_SOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(TRACE_API_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/trace/src) set(METRICS_API_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/metrics/src) +set(LOGS_API_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/logs/src) set(CONTEXT_API_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/context/src) set(BAGGAGE_API_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/baggage/src) set(COMMON_API_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/api/common/src) set(TRACE_SDK_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/trace/src) set(METRICS_SDK_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/metrics/src) +set(LOGS_SDK_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/logs/src) set(COMMON_SDK_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/sdk/common/src) set(OTLP_EXPORTER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/src) set(OPENTELEMETRY_PROXY_SOURCES @@ -261,6 +267,8 @@ set(OPENTELEMETRY_PROXY_SOURCES ${METRICS_API_SOURCE_DIR}/MeasurementFetcher.cpp ${METRICS_API_SOURCE_DIR}/AsynchronousInstrumentProxy.cpp ${METRICS_API_SOURCE_DIR}/AsynchronousInstrumentProxyFactory.cpp + ${LOGS_API_SOURCE_DIR}/LoggerProviderProxy.cpp + ${LOGS_API_SOURCE_DIR}/LoggerProxy.cpp ${CONTEXT_API_SOURCE_DIR}/TextMapPropagatorProxy.cpp ${CONTEXT_API_SOURCE_DIR}/CompositePropagatorProxy.cpp ${CONTEXT_API_SOURCE_DIR}/TextMapCarrierProxy.cpp @@ -273,18 +281,23 @@ set(OPENTELEMETRY_PROXY_SOURCES ${METRICS_SDK_SOURCE_DIR}/MeterProviderProxy.cpp ${METRICS_SDK_SOURCE_DIR}/ViewProxy.cpp ${METRICS_SDK_SOURCE_DIR}/PeriodicExportingMetricReaderProxy.cpp + ${LOGS_SDK_SOURCE_DIR}/LoggerProviderProxy.cpp + ${LOGS_SDK_SOURCE_DIR}/SimpleLogRecordProcessorProxy.cpp + ${LOGS_SDK_SOURCE_DIR}/BatchLogRecordProcessorProxy.cpp ${COMMON_SDK_SOURCE_DIR}/resource.cpp) if(WITH_OTLP_HTTP) set(OPENTELEMETRY_PROXY_SOURCES ${OPENTELEMETRY_PROXY_SOURCES} ${OTLP_EXPORTER_SOURCE_DIR}/OtlpHttpSpanExporterProxy.cpp - ${OTLP_EXPORTER_SOURCE_DIR}/OtlpHttpMetricExporterProxy.cpp) + ${OTLP_EXPORTER_SOURCE_DIR}/OtlpHttpMetricExporterProxy.cpp + ${OTLP_EXPORTER_SOURCE_DIR}/OtlpHttpLogRecordExporterProxy.cpp) endif() if(WITH_OTLP_GRPC) set(OPENTELEMETRY_PROXY_SOURCES ${OPENTELEMETRY_PROXY_SOURCES} ${OTLP_EXPORTER_SOURCE_DIR}/OtlpGrpcSpanExporterProxy.cpp - ${OTLP_EXPORTER_SOURCE_DIR}/OtlpGrpcMetricExporterProxy.cpp) + ${OTLP_EXPORTER_SOURCE_DIR}/OtlpGrpcMetricExporterProxy.cpp + ${OTLP_EXPORTER_SOURCE_DIR}/OtlpGrpcLogRecordExporterProxy.cpp) endif() libmexclass_client_add_proxy_library( @@ -417,23 +430,28 @@ libmexclass_client_install( # Install M files set(TRACE_API_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/api/trace/+opentelemetry) set(METRICS_API_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/api/metrics/+opentelemetry) +set(LOGS_API_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/api/logs/+opentelemetry) set(CONTEXT_API_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/api/context/+opentelemetry) set(BAGGAGE_API_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/api/baggage/+opentelemetry) set(COMMON_API_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/api/common/+opentelemetry) set(TRACE_SDK_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sdk/trace/+opentelemetry) set(METRICS_SDK_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sdk/metrics/+opentelemetry) +set(LOGS_SDK_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sdk/logs/+opentelemetry) set(COMMON_SDK_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sdk/common/+opentelemetry) set(EXPORTER_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/defaultSpanExporter.m ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/defaultMetricExporter.m + ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/defaultLogRecordExporter.m ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpValidator.m) set(OTLP_HTTP_EXPORTER_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m + ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpValidator.m) set(OTLP_GRPC_EXPORTER_MATLAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcSpanExporter.m ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcMetricExporter.m + ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcLogRecordExporter.m ${CMAKE_CURRENT_SOURCE_DIR}/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcValidator.m) set(OTLP_MISC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt) @@ -441,11 +459,13 @@ set(OTLP_EXPORTERS_DIR +opentelemetry/+exporters/+otlp) install(DIRECTORY ${TRACE_API_MATLAB_SOURCES} DESTINATION .) install(DIRECTORY ${METRICS_API_MATLAB_SOURCES} DESTINATION .) +install(DIRECTORY ${LOGS_API_MATLAB_SOURCES} DESTINATION .) install(DIRECTORY ${CONTEXT_API_MATLAB_SOURCES} DESTINATION .) install(DIRECTORY ${BAGGAGE_API_MATLAB_SOURCES} DESTINATION .) install(DIRECTORY ${COMMON_API_MATLAB_SOURCES} DESTINATION .) install(DIRECTORY ${TRACE_SDK_MATLAB_SOURCES} DESTINATION .) install(DIRECTORY ${METRICS_SDK_MATLAB_SOURCES} DESTINATION .) +install(DIRECTORY ${LOGS_SDK_MATLAB_SOURCES} DESTINATION .) install(DIRECTORY ${COMMON_SDK_MATLAB_SOURCES} DESTINATION .) install(FILES ${EXPORTER_MATLAB_SOURCES} DESTINATION ${OTLP_EXPORTERS_DIR}) if(WITH_OTLP_HTTP) diff --git a/OtelMatlabProxyFactory.cpp b/OtelMatlabProxyFactory.cpp index 15b023b..9e8f90b 100644 --- a/OtelMatlabProxyFactory.cpp +++ b/OtelMatlabProxyFactory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #include "OtelMatlabProxyFactory.h" @@ -8,6 +8,7 @@ //#include "opentelemetry-matlab/trace/ScopeProxy.h" #include "opentelemetry-matlab/trace/SpanContextProxy.h" #include "opentelemetry-matlab/trace/TraceContextPropagatorProxy.h" +#include "opentelemetry-matlab/logs/LoggerProviderProxy.h" #include "opentelemetry-matlab/context/propagation/TextMapCarrierProxy.h" #include "opentelemetry-matlab/context/propagation/TextMapPropagatorProxy.h" #include "opentelemetry-matlab/context/propagation/CompositePropagatorProxy.h" @@ -25,19 +26,25 @@ #include "opentelemetry-matlab/sdk/metrics/MeterProviderProxy.h" #include "opentelemetry-matlab/sdk/metrics/ViewProxy.h" #include "opentelemetry-matlab/sdk/metrics/PeriodicExportingMetricReaderProxy.h" +#include "opentelemetry-matlab/sdk/logs/LoggerProviderProxy.h" +#include "opentelemetry-matlab/sdk/logs/SimpleLogRecordProcessorProxy.h" +#include "opentelemetry-matlab/sdk/logs/BatchLogRecordProcessorProxy.h" #ifdef WITH_OTLP_HTTP #include "opentelemetry-matlab/exporters/otlp/OtlpHttpSpanExporterProxy.h" #include "opentelemetry-matlab/exporters/otlp/OtlpHttpMetricExporterProxy.h" + #include "opentelemetry-matlab/exporters/otlp/OtlpHttpLogRecordExporterProxy.h" #endif #ifdef WITH_OTLP_GRPC #include "opentelemetry-matlab/exporters/otlp/OtlpGrpcSpanExporterProxy.h" #include "opentelemetry-matlab/exporters/otlp/OtlpGrpcMetricExporterProxy.h" + #include "opentelemetry-matlab/exporters/otlp/OtlpGrpcLogRecordExporterProxy.h" #endif libmexclass::proxy::MakeResult OtelMatlabProxyFactory::make_proxy(const libmexclass::proxy::ClassName& class_name, const libmexclass::proxy::FunctionArguments& constructor_arguments) { + REGISTER_PROXY(libmexclass.opentelemetry.LoggerProviderProxy, libmexclass::opentelemetry::LoggerProviderProxy); REGISTER_PROXY(libmexclass.opentelemetry.MeterProviderProxy, libmexclass::opentelemetry::MeterProviderProxy); REGISTER_PROXY(libmexclass.opentelemetry.TracerProviderProxy, libmexclass::opentelemetry::TracerProviderProxy); //REGISTER_PROXY(libmexclass.opentelemetry.TracerProxy, libmexclass::opentelemetry::TracerProxy); @@ -65,13 +72,19 @@ OtelMatlabProxyFactory::make_proxy(const libmexclass::proxy::ClassName& class_na REGISTER_PROXY(libmexclass.opentelemetry.sdk.ViewProxy, libmexclass::opentelemetry::sdk::ViewProxy); REGISTER_PROXY(libmexclass.opentelemetry.sdk.PeriodicExportingMetricReaderProxy, libmexclass::opentelemetry::sdk::PeriodicExportingMetricReaderProxy); + REGISTER_PROXY(libmexclass.opentelemetry.sdk.LoggerProviderProxy, libmexclass::opentelemetry::sdk::LoggerProviderProxy); + REGISTER_PROXY(libmexclass.opentelemetry.sdk.SimpleLogRecordProcessorProxy, libmexclass::opentelemetry::sdk::SimpleLogRecordProcessorProxy); + REGISTER_PROXY(libmexclass.opentelemetry.sdk.BatchLogRecordProcessorProxy, libmexclass::opentelemetry::sdk::BatchLogRecordProcessorProxy); + #ifdef WITH_OTLP_HTTP REGISTER_PROXY(libmexclass.opentelemetry.exporters.OtlpHttpSpanExporterProxy, libmexclass::opentelemetry::exporters::OtlpHttpSpanExporterProxy); REGISTER_PROXY(libmexclass.opentelemetry.exporters.OtlpHttpMetricExporterProxy, libmexclass::opentelemetry::exporters::OtlpHttpMetricExporterProxy); + REGISTER_PROXY(libmexclass.opentelemetry.exporters.OtlpHttpLogRecordExporterProxy, libmexclass::opentelemetry::exporters::OtlpHttpLogRecordExporterProxy); #endif #ifdef WITH_OTLP_GRPC REGISTER_PROXY(libmexclass.opentelemetry.exporters.OtlpGrpcSpanExporterProxy, libmexclass::opentelemetry::exporters::OtlpGrpcSpanExporterProxy); REGISTER_PROXY(libmexclass.opentelemetry.exporters.OtlpGrpcMetricExporterProxy, libmexclass::opentelemetry::exporters::OtlpGrpcMetricExporterProxy); + REGISTER_PROXY(libmexclass.opentelemetry.exporters.OtlpGrpcLogRecordExporterProxy, libmexclass::opentelemetry::exporters::OtlpGrpcLogRecordExporterProxy); #endif return nullptr; } diff --git a/api/context/+opentelemetry/+context/Context.m b/api/context/+opentelemetry/+context/Context.m index 6afb8f5..424202c 100644 --- a/api/context/+opentelemetry/+context/Context.m +++ b/api/context/+opentelemetry/+context/Context.m @@ -2,11 +2,11 @@ % Propagation mechanism used to carry context data across functions and % external interfaces. -% Copyright 2023 The MathWorks, Inc. +% Copyright 2023-2024 The MathWorks, Inc. properties (Access={?opentelemetry.context.propagation.TextMapPropagator, ... ?opentelemetry.trace.Span, ?opentelemetry.trace.Tracer, ... - ?opentelemetry.baggage.Baggage}) + ?opentelemetry.logs.Logger, ?opentelemetry.baggage.Baggage}) Proxy % Proxy object to interface C++ code end diff --git a/api/logs/+opentelemetry/+logs/Logger.m b/api/logs/+opentelemetry/+logs/Logger.m new file mode 100644 index 0000000..150bba0 --- /dev/null +++ b/api/logs/+opentelemetry/+logs/Logger.m @@ -0,0 +1,257 @@ +classdef Logger < handle + % A logger that is used to emit log records + + % Copyright 2024 The MathWorks, Inc. + + properties (SetAccess=immutable) + Name (1,1) string % Logger name + Version (1,1) string % Logger version + Schema (1,1) string % URL that documents the schema of the generated log records + end + + properties (Access=private) + Proxy % Proxy object to interface C++ code + end + + methods (Access={?opentelemetry.logs.LoggerProvider, ?opentelemetry.sdk.logs.LoggerProvider}) + function obj = Logger(proxy, lgname, lgversion, lgschema) + % Private constructor. Use getLogger method of LoggerProvider + % to create loggers. + obj.Proxy = proxy; + obj.Name = lgname; + obj.Version = lgversion; + obj.Schema = lgschema; + end + end + + methods + function emitLogRecord(obj, severity, content, trailingnames, trailingvalues) + % EMITLOGRECORD Create and emit a log record + % EMITLOGRECORD(LG, SEVERITY, CONTENT) emits a log record + % with the specified severity and content. Severity is one + % of "trace", "debug", "info", "warn", "error", and "fatal". It + % can also be a scalar integer between 1 and 24. Content can be an + % array of type double, int32, uint32, int64, logical, or string. + % + % EMITLOGRECORD(LG, SEVERITY, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % ...) specifies optional parameter name/value pairs. + % Parameters are: + % "Context" - Span contained in a context object. + % "Timestamp" - Timestamp of the log record specified as a + % datetime. Default is the current time. + % "Attributes" - Attribute name-value pairs specified as + % a dictionary. + % + % See also OPENTELEMETRY.LOGS.TRACE, OPENTELEMETRY.LOGS.DEBUG, + % OPENTELEMETRY.LOGS.INFO, OPENTELEMETRY.LOGS.WARN, + % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL + arguments + obj + severity + content + end + arguments (Repeating) + trailingnames + trailingvalues + end + if isnumeric(severity) && isscalar(severity) + if severity < 1 || severity > 24 || round(severity) ~= severity + severity = 0; % invalid + end + elseif (isstring(severity) && isscalar(severity)) || ... + (ischar(severity) && isrow(severity)) + severitylist = ["trace", "debug", "info", "warn", "error", "fatal"]; + severitylist = reshape(severitylist + [""; "2"; "3"; "4"], 1, []); + d = dictionary(severitylist, 1:length(severitylist)); + try + severity = d(lower(severity)); + catch + severity = 0; % invalid + end + else + severity = 0; + end + try + content = string(content); + catch + content = ""; + end + + % validate the trailing names and values + optionnames = ["Context", "Timestamp", "Attributes"]; + + % define default values + contextid = intmax("uint64"); % default value which means no context supplied + timestamp = NaN; + attributekeys = string.empty(); + attributevalues = {}; + + % Loop through Name-Value pairs + for i = 1:length(trailingnames) + try + namei = validatestring(trailingnames{i}, optionnames); + catch + % invalid option, ignore + continue + end + if strcmp(namei, "Context") + context = trailingvalues{i}; + if isa(context, "opentelemetry.context.Context") + contextid = context.Proxy.ID; + end + elseif strcmp(namei, "Timestamp") + valuei = trailingvalues{i}; + if isdatetime(valuei) && isscalar(valuei) && ~isnat(valuei) + timestamp = posixtime(valuei); + end + elseif strcmp(namei, "Attributes") + valuei = trailingvalues{i}; + if isa(valuei, "dictionary") + attributekeys = keys(valuei); + attributevalues = values(valuei,"cell"); + % collapse one level of cells, as this may be due to + % a behavior of dictionary.values + if all(cellfun(@iscell, attributevalues)) + attributevalues = [attributevalues{:}]; + end + end + end + end + obj.Proxy.emitLogRecord(severity, content, contextid, timestamp, ... + attributekeys, attributevalues); + end + + function trace(obj, content, varargin) + % TRACE Create and emit a log record with "trace" severity + % TRACE(LG, CONTENT) emits a log record with "trace" severity. + % Content can be an array of type double, int32, uint32, + % int64, logical, or string. + % + % TRACE(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % ...) specifies optional parameter name/value pairs. + % Parameters are: + % "Context" - Span contained in a context object. + % "Timestamp" - Timestamp of the log record specified as a + % datetime. Default is the current time. + % "Attributes" - Attribute name-value pairs specified as + % a dictionary. + % + % See also OPENTELEMETRY.LOGS.DEBUG, + % OPENTELEMETRY.LOGS.INFO, OPENTELEMETRY.LOGS.WARN, + % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL, + % OPENTELEMETRY.LOGS.EMITLOGRECORD + emitLogRecord(obj, "trace", content, varargin{:}); + end + + function debug(obj, content, varargin) + % DEBUG Create and emit a log record with "debug" severity + % DEBUG(LG, CONTENT) emits a log record with "debug" severity. + % Content can be an array of type double, int32, uint32, + % int64, logical, or string. + % + % DEBUG(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % ...) specifies optional parameter name/value pairs. + % Parameters are: + % "Context" - Span contained in a context object. + % "Timestamp" - Timestamp of the log record specified as a + % datetime. Default is the current time. + % "Attributes" - Attribute name-value pairs specified as + % a dictionary. + % + % See also OPENTELEMETRY.LOGS.TRACE, + % OPENTELEMETRY.LOGS.INFO, OPENTELEMETRY.LOGS.WARN, + % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL, + % OPENTELEMETRY.LOGS.EMITLOGRECORD + emitLogRecord(obj, "debug", content, varargin{:}); + end + + function info(obj, content, varargin) + % INFO Create and emit a log record with "info" severity + % INFO(LG, CONTENT) emits a log record with "info" severity. + % Content can be an array of type double, int32, uint32, + % int64, logical, or string. + % + % INFO(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % ...) specifies optional parameter name/value pairs. + % Parameters are: + % "Context" - Span contained in a context object. + % "Timestamp" - Timestamp of the log record specified as a + % datetime. Default is the current time. + % "Attributes" - Attribute name-value pairs specified as + % a dictionary. + % + % See also OPENTELEMETRY.LOGS.TRACE, + % OPENTELEMETRY.LOGS.DEBUG, OPENTELEMETRY.LOGS.WARN, + % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL, + % OPENTELEMETRY.LOGS.EMITLOGRECORD + emitLogRecord(obj, "info", content, varargin{:}); + end + + function warn(obj, content, varargin) + % WARN Create and emit a log record with "warn" severity + % WARN(LG, CONTENT) emits a log record with "warn" severity. + % Content can be an array of type double, int32, uint32, + % int64, logical, or string. + % + % WARN(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % ...) specifies optional parameter name/value pairs. + % Parameters are: + % "Context" - Span contained in a context object. + % "Timestamp" - Timestamp of the log record specified as a + % datetime. Default is the current time. + % "Attributes" - Attribute name-value pairs specified as + % a dictionary. + % + % See also OPENTELEMETRY.LOGS.TRACE, + % OPENTELEMETRY.LOGS.DEBUG, OPENTELEMETRY.LOGS.INFO, + % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL, + % OPENTELEMETRY.LOGS.EMITLOGRECORD + emitLogRecord(obj, "warn", content, varargin{:}); + end + + function error(obj, content, varargin) + % ERROR Create and emit a log record with "error" severity + % ERROR(LG, CONTENT) emits a log record with "error" severity. + % Content can be an array of type double, int32, uint32, + % int64, logical, or string. + % + % ERROR(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % ...) specifies optional parameter name/value pairs. + % Parameters are: + % "Context" - Span contained in a context object. + % "Timestamp" - Timestamp of the log record specified as a + % datetime. Default is the current time. + % "Attributes" - Attribute name-value pairs specified as + % a dictionary. + % + % See also OPENTELEMETRY.LOGS.TRACE, + % OPENTELEMETRY.LOGS.DEBUG, OPENTELEMETRY.LOGS.INFO, + % OPENTELEMETRY.LOGS.WARN, OPENTELEMETRY.LOGS.FATAL, + % OPENTELEMETRY.LOGS.EMITLOGRECORD + emitLogRecord(obj, "error", content, varargin{:}); + end + + function fatal(obj, content, varargin) + % FATAL Create and emit a log record with "fatal" severity + % FATAL(LG, CONTENT) emits a log record with "fatal" severity. + % Content can be an array of type double, int32, uint32, + % int64, logical, or string. + % + % FATAL(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % ...) specifies optional parameter name/value pairs. + % Parameters are: + % "Context" - Span contained in a context object. + % "Timestamp" - Timestamp of the log record specified as a + % datetime. Default is the current time. + % "Attributes" - Attribute name-value pairs specified as + % a dictionary. + % + % See also OPENTELEMETRY.LOGS.TRACE, + % OPENTELEMETRY.LOGS.DEBUG, OPENTELEMETRY.LOGS.INFO, + % OPENTELEMETRY.LOGS.WARN, OPENTELEMETRY.LOGS.ERROR, + % OPENTELEMETRY.LOGS.EMITLOGRECORD + emitLogRecord(obj, "fatal", content, varargin{:}); + end + end + +end diff --git a/api/logs/+opentelemetry/+logs/LoggerProvider.m b/api/logs/+opentelemetry/+logs/LoggerProvider.m new file mode 100644 index 0000000..9a02209 --- /dev/null +++ b/api/logs/+opentelemetry/+logs/LoggerProvider.m @@ -0,0 +1,70 @@ +classdef LoggerProvider < handle + % A logger provider stores a set of configurations used in a log + % system. + + % Copyright 2024 The MathWorks, Inc. + + properties (Access={?opentelemetry.sdk.logs.LoggerProvider, ... + ?opentelemetry.sdk.common.Cleanup}) + Proxy % Proxy object to interface C++ code + end + + methods (Access={?opentelemetry.logs.Provider, ?opentelemetry.sdk.logs.LoggerProvider}) + function obj = LoggerProvider(skip) + % constructor + % "skip" input signals skipping construction + if nargin < 1 || skip ~= "skip" + obj.Proxy = libmexclass.proxy.Proxy("Name", ... + "libmexclass.opentelemetry.LoggerProviderProxy", ... + "ConstructorArguments", {}); + end + end + end + + methods + function logger = getLogger(obj, lgname, lgversion, lgschema) + % GETLOGGER Create a logger object used to generate logs. + % LG = GETLOGGER(LP, NAME) returns a logger with the name + % NAME that uses all the configurations specified in logger + % provider LP. + % + % LG = GETLOGGER(LP, NAME, VERSION, SCHEMA) also specifies + % the logger version and the URL that documents the schema + % of the generated logs. + % + % See also OPENTELEMETRY.LOGS.LOGGER + arguments + obj + lgname + lgversion = "" + lgschema = "" + end + % name, version, schema accepts any types that can convert to a + % string + import opentelemetry.common.mustBeScalarString + lgname = mustBeScalarString(lgname); + lgversion = mustBeScalarString(lgversion); + lgschema = mustBeScalarString(lgschema); + id = obj.Proxy.getLogger(lgname, lgversion, lgschema); + loggerproxy = libmexclass.proxy.Proxy("Name", ... + "libmexclass.opentelemetry.LoggerProxy", "ID", id); + logger = opentelemetry.logs.Logger(loggerproxy, lgname, lgversion, lgschema); + end + + function setLoggerProvider(obj) + % SETLOGGERPROVIDER Set global instance of logger provider + % SETLOGGERPROVIDER(LP) sets the logger provider LP as + % the global instance. + % + % See also OPENTELEMETRY.LOGS.PROVIDER.GETLOGGERPROVIDER + obj.Proxy.setLoggerProvider(); + end + end + + methods(Access=?opentelemetry.sdk.common.Cleanup) + function postShutdown(obj) + % POSTSHUTDOWN Handle post-shutdown tasks + obj.Proxy.postShutdown(); + end + end +end diff --git a/api/logs/+opentelemetry/+logs/Provider.m b/api/logs/+opentelemetry/+logs/Provider.m new file mode 100644 index 0000000..6a54653 --- /dev/null +++ b/api/logs/+opentelemetry/+logs/Provider.m @@ -0,0 +1,28 @@ +classdef Provider +% Get and set the global instance of logger provider + +% Copyright 2024 The MathWorks, Inc. + + methods (Static) + function p = getLoggerProvider() + % Get the global instance of logger provider + % LP = OPENTELEMETRY.LOGS.PROVIDER.GETLOGGERPROVIDER gets + % the global instance of logger provider. + % + % See also OPENTELEMETRY.LOGS.PROVIDER.SETLOGGERPROVIDER + + p = opentelemetry.logs.LoggerProvider(); + end + + function setLoggerProvider(p) + % Set the global instance of logger provider + % OPENTELEMETRY.LOGS.PROVIDER.SETLOGGERPROVIDER(LP) sets + % LP as the global instance of logger provider. + % + % See also OPENTELEMETRY.LOGS.PROVIDER.GETTRACERPROVIDER + + p.setLoggerProvider(); + end + end + +end diff --git a/api/logs/+opentelemetry/+logs/getLogger.m b/api/logs/+opentelemetry/+logs/getLogger.m new file mode 100644 index 0000000..e12d0b8 --- /dev/null +++ b/api/logs/+opentelemetry/+logs/getLogger.m @@ -0,0 +1,17 @@ +function logger = getLogger(trname, varargin) +% Create a logger from the global logger provider instance +% LG = OPENTELEMETRY.LOGS.GETLOGGER(NAME) returns a logger with the +% specified name created from the global logger provider instance. +% +% LG = OPENTELEMETRY.LOGS.GETLOGGER(NAME, VERSION, SCHEMA) also +% specifies the logger version and the URL that documents the schema +% of the generated log records. +% +% See also OPENTELEMETRY.SDK.LOGS.LOGGERPROVIDER, +% OPENTELEMETRY.LOGS.LOGGER, +% OPENTELEMETRY.LOGS.PROVIDER.SETLOGGERPROVIDER + +% Copyright 2024 The MathWorks, Inc. + +provider = opentelemetry.logs.Provider.getLoggerProvider(); +logger = getLogger(provider, trname, varargin{:}); diff --git a/api/logs/include/opentelemetry-matlab/logs/LoggerProviderProxy.h b/api/logs/include/opentelemetry-matlab/logs/LoggerProviderProxy.h new file mode 100644 index 0000000..c90fbb4 --- /dev/null +++ b/api/logs/include/opentelemetry-matlab/logs/LoggerProviderProxy.h @@ -0,0 +1,47 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "libmexclass/proxy/Proxy.h" +#include "libmexclass/proxy/method/Context.h" + +#include "opentelemetry/logs/logger_provider.h" +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/logs/noop.h" + +namespace logs_api = opentelemetry::logs; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { +class LoggerProviderProxy : public libmexclass::proxy::Proxy { + public: + LoggerProviderProxy(nostd::shared_ptr lp) : CppLoggerProvider(lp) { + REGISTER_METHOD(LoggerProviderProxy, getLogger); + REGISTER_METHOD(LoggerProviderProxy, setLoggerProvider); + REGISTER_METHOD(LoggerProviderProxy, postShutdown); + } + + // Static make method should only be used by getLoggerProvider. It gets the global instance + // instead of creating a new instance + static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { + return std::make_shared(logs_api::Provider::GetLoggerProvider()); + } + + void getLogger(libmexclass::proxy::method::Context& context); + + void setLoggerProvider(libmexclass::proxy::method::Context& context); + + nostd::shared_ptr getInstance() { + return CppLoggerProvider; + } + + void postShutdown(libmexclass::proxy::method::Context& context) { + // // Replace logger provider with a no-op instance. Subsequent logs won't be recorded + nostd::shared_ptr noop(new logs_api::NoopLoggerProvider); + CppLoggerProvider.swap(noop); + } + + protected: + nostd::shared_ptr CppLoggerProvider; +}; +} // namespace libmexclass::opentelemetry diff --git a/api/logs/include/opentelemetry-matlab/logs/LoggerProxy.h b/api/logs/include/opentelemetry-matlab/logs/LoggerProxy.h new file mode 100644 index 0000000..039a477 --- /dev/null +++ b/api/logs/include/opentelemetry-matlab/logs/LoggerProxy.h @@ -0,0 +1,26 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "libmexclass/proxy/Proxy.h" +#include "libmexclass/proxy/method/Context.h" + +#include "opentelemetry/logs/logger.h" + +namespace logs_api = opentelemetry::logs; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { +class LoggerProxy : public libmexclass::proxy::Proxy { + public: + LoggerProxy(nostd::shared_ptr lg) : CppLogger(lg) { + REGISTER_METHOD(LoggerProxy, emitLogRecord); + } + + void emitLogRecord(libmexclass::proxy::method::Context& context); + + private: + + nostd::shared_ptr CppLogger; +}; +} // namespace libmexclass::opentelemetry diff --git a/api/logs/src/LoggerProviderProxy.cpp b/api/logs/src/LoggerProviderProxy.cpp new file mode 100644 index 0000000..9ba917b --- /dev/null +++ b/api/logs/src/LoggerProviderProxy.cpp @@ -0,0 +1,39 @@ +// Copyright 2024 The MathWorks, Inc. + +#include "opentelemetry-matlab/logs/LoggerProviderProxy.h" +#include "opentelemetry-matlab/logs/LoggerProxy.h" +#include "libmexclass/proxy/ProxyManager.h" + +#include "opentelemetry/logs/provider.h" + +#include "MatlabDataArray.hpp" +namespace libmexclass::opentelemetry { +void LoggerProviderProxy::getLogger(libmexclass::proxy::method::Context& context) { + // Always assumes 3 inputs + matlab::data::StringArray name_mda = context.inputs[0]; + std::string name = static_cast(name_mda[0]); + matlab::data::StringArray version_mda = context.inputs[1]; + std::string version = static_cast(version_mda[0]); + matlab::data::StringArray schema_mda = context.inputs[2]; + std::string schema = static_cast(schema_mda[0]); + + auto lg = CppLoggerProvider->GetLogger(name, version, schema); + + // instantiate a LoggerProxy instance + LoggerProxy* newproxy = new LoggerProxy(lg); + auto lgproxy = std::shared_ptr(newproxy); + + // obtain a proxy ID + libmexclass::proxy::ID proxyid = libmexclass::proxy::ProxyManager::manageProxy(lgproxy); + + // return the ID + matlab::data::ArrayFactory factory; + auto proxyid_mda = factory.createScalar(proxyid); + context.outputs[0] = proxyid_mda; +} + +void LoggerProviderProxy::setLoggerProvider(libmexclass::proxy::method::Context& context) { + logs_api::Provider::SetLoggerProvider(CppLoggerProvider); +} + +} // namespace libmexclass::opentelemetry diff --git a/api/logs/src/LoggerProxy.cpp b/api/logs/src/LoggerProxy.cpp new file mode 100644 index 0000000..71308c9 --- /dev/null +++ b/api/logs/src/LoggerProxy.cpp @@ -0,0 +1,72 @@ +// Copyright 2024 The MathWorks, Inc. + +#include "opentelemetry-matlab/logs/LoggerProxy.h" +#include "opentelemetry-matlab/common/attribute.h" +#include "opentelemetry-matlab/context/ContextProxy.h" +#include "libmexclass/proxy/ProxyManager.h" + +#include "opentelemetry/logs/severity.h" +#include "opentelemetry/logs/log_record.h" +#include "opentelemetry/trace/context.h" +#include "opentelemetry/context/Context.h" + +#include "MatlabDataArray.hpp" + +//#include + +namespace logs_api = opentelemetry::logs; +namespace trace_api = opentelemetry::trace; +namespace context_api = opentelemetry::context; +namespace common = opentelemetry::common; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry { +void LoggerProxy::emitLogRecord(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray severity_mda = context.inputs[0]; + int severity = static_cast(severity_mda[0]); + matlab::data::StringArray content_mda = context.inputs[1]; + std::string content = static_cast(content_mda[0]); + matlab::data::TypedArray contextid_mda = context.inputs[2]; + libmexclass::proxy::ID contextid = contextid_mda[0]; + libmexclass::proxy::ID nocontextid(-1); // wrap around to intmax + matlab::data::TypedArray timestamp_mda = context.inputs[3]; + double timestamp = timestamp_mda[0]; // number of seconds since 1/1/1970 (i.e. POSIX time) + matlab::data::StringArray attrnames_mda = context.inputs[4]; + size_t nattrs = attrnames_mda.getNumberOfElements(); + matlab::data::CellArray attrvalues_mda = context.inputs[5]; + + nostd::unique_ptr rec = CppLogger->CreateLogRecord(); + + // context + if (contextid != nocontextid) { + context_api::Context supplied_context = std::static_pointer_cast( + libmexclass::proxy::ProxyManager::getProxy(contextid))->getInstance(); + trace_api::SpanContext sc = trace_api::GetSpan(supplied_context)->GetContext(); + rec->SetTraceId(sc.trace_id()); + rec->SetSpanId(sc.span_id()); + rec->SetTraceFlags(sc.trace_flags()); + } + + // timestamp + if (timestamp == timestamp) { // not NaN. NaN means not specified + rec->SetTimestamp(common::SystemTimestamp{std::chrono::duration(timestamp)}); + } + + // attributes + ProcessedAttributes attrs; + if (nattrs > 0) { + for (size_t i = 0; i < nattrs; ++i) { + std::string attrname = static_cast(attrnames_mda[i]); + matlab::data::Array attrvalue = attrvalues_mda[i]; + + processAttribute(attrname, attrvalue, attrs); + } + auto record_attribute = [&](const std::pair attr) + {rec->SetAttribute(attr.first, attr.second);}; + std::for_each(attrs.Attributes.cbegin(), attrs.Attributes.cend(), record_attribute); + } + + CppLogger->EmitLogRecord(std::move(rec), static_cast(severity), nostd::string_view{content}); + +} +} // namespace libmexclass::opentelemetry diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcLogRecordExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcLogRecordExporter.m new file mode 100644 index 0000000..4887c07 --- /dev/null +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpGrpcLogRecordExporter.m @@ -0,0 +1,99 @@ +classdef OtlpGrpcLogRecordExporter < opentelemetry.sdk.logs.LogRecordExporter +% OtlpGrpcLogRecordExporter exports log records in OpenTelemetry Protocol format via +% gRPC. By default, it exports to the default address of the OpenTelemetry +% Collector. + +% Copyright 2024 The MathWorks, Inc. + + properties + Endpoint (1,1) string = "http://localhost:4317" % Export destination + UseCredentials (1,1) logical = false % Whether to use SSL credentials + CertificatePath (1,1) string = "" % Path to .pem file for SSL encryption + CertificateString (1,1) string = "" % In-memory string representation of .pem file for SSL encryption + Timeout (1,1) duration = seconds(10) % Maximum time above which exports will abort + HttpHeaders (1,1) dictionary = dictionary(string.empty, string.empty) % Additional HTTP headers + end + + properties (Constant) + Validator = opentelemetry.exporters.otlp.OtlpGrpcValidator + end + + methods + function obj = OtlpGrpcLogRecordExporter(optionnames, optionvalues) + % OtlpGrpcLogRecordExporter exports log records in OpenTelemetry + % Protocol format via gRPC. + % EXP = OPENTELEMETRY.EXPORTERS.OTLP.OTLPGRPCLOGRECORDEXPORTER + % creates an exporter that uses default configurations. + % + % EXP = + % OPENTELEMETRY.EXPORTERS.OTLP.OTLPGRPCLOGRECORDEXPORTER(PARAM1, + % VALUE1, PARAM2, VALUE2, ...) specifies optional parameter + % name/value pairs. Parameters are: + % "Endpoint" - Endpoint to export to + % "UseCredentials" - Whether to use SSL credentials. + % Default is false. If true, use + % .pem file specified in + % "CertificatePath" or + % "CertificateString". + % "CertificatePath" - Path to .pem file for SSL encryption + % "CertificateString" - .pem file specified in memory as + % a string + % "Timeout" - Maximum time above which exports + % will abort + % "HTTPHeaders" - Additional HTTP Headers + % + % See also OPENTELEMETRY.EXPORTERS.OTLP.OTLPHTTPLOGRECORDEXPORTER + arguments (Repeating) + optionnames (1,:) {mustBeTextScalar} + optionvalues + end + + obj = obj@opentelemetry.sdk.logs.LogRecordExporter(... + "libmexclass.opentelemetry.exporters.OtlpGrpcLogRecordExporterProxy"); + + validnames = ["Endpoint", "UseCredentials ", "CertificatePath", ... + "CertificateString", "Timeout", "HttpHeaders"]; + for i = 1:length(optionnames) + namei = validatestring(optionnames{i}, validnames); + valuei = optionvalues{i}; + obj.(namei) = valuei; + end + end + + function obj = set.Endpoint(obj, ep) + ep = obj.Validator.validateEndpoint(ep); + obj.Proxy.setEndpoint(ep); + obj.Endpoint = ep; + end + + function obj = set.UseCredentials(obj, uc) + uc = obj.Validator.validateUseCredentials(uc); + obj.Proxy.setUseCredentials(uc); + obj.UseCredentials = uc; + end + + function obj = set.CertificatePath(obj, certpath) + certpath = obj.Validator.validateCertificatePath(certpath); + obj.Proxy.setCertificatePath(certpath); + obj.CertificatePath = certpath; + end + + function obj = set.CertificateString(obj, certstr) + certstr = obj.Validator.validateCertificateString(certstr); + obj.Proxy.setCertificateString(certstr); + obj.CertificateString = certstr; + end + + function obj = set.Timeout(obj, timeout) + obj.Validator.validateTimeout(timeout); + obj.Proxy.setTimeout(milliseconds(timeout)); + obj.Timeout = timeout; + end + + function obj = set.HttpHeaders(obj, httpheaders) + [headerkeys, headervalues] = obj.Validator.validateHttpHeaders(httpheaders); + obj.Proxy.setHttpHeaders(headerkeys, headervalues); + obj.HttpHeaders = httpheaders; + end + end +end diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m new file mode 100644 index 0000000..f3bd840 --- /dev/null +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m @@ -0,0 +1,98 @@ +classdef OtlpHttpLogRecordExporter < opentelemetry.sdk.logs.LogRecordExporter +% OtlpHttpLogRecordExporter exports log records in OpenTelemetry Protocol format via +% HTTP. By default, it exports to the default address of the OpenTelemetry +% Collector. + +% Copyright 2024 The MathWorks, Inc. + + properties + Endpoint (1,1) string = "http://localhost:4318/v1/logs" % Export destination + Format (1,1) string = "JSON" % Data format, JSON or binary + JsonBytesMapping (1,1) string = "hexId" % What to convert JSON bytes to + UseJsonName (1,1) logical = false % Whether to use JSON name of protobuf field to set the key of JSON + Timeout (1,1) duration = seconds(10) % Maximum time above which exports will abort + HttpHeaders (1,1) dictionary = dictionary(string.empty, string.empty) % Additional HTTP headers + end + + properties (Constant) + Validator = opentelemetry.exporters.otlp.OtlpHttpValidator + end + + methods + function obj = OtlpHttpLogRecordExporter(optionnames, optionvalues) + % OtlpHttpLogRecordExporter exports log records in OpenTelemetry Protocol format via HTTP. + % EXP = OPENTELEMETRY.EXPORTERS.OTLP.OTLPHTTPLOGRECORDEXPORTER + % creates an exporter that uses default configurations. + % + % EXP = + % OPENTELEMETRY.EXPORTERS.OTLP.OTLPHTTPLOGRECORDEXPORTER(PARAM1, + % VALUE1, PARAM2, VALUE2, ...) specifies optional parameter + % name/value pairs. Parameters are: + % "Endpoint" - Endpoint to export to + % "Format" - Data format: "JSON" (default) or "binary" + % "JsonBytesMapping" - What to convert JSON bytes to. Supported + % values are "hex", "hexId" (default), and + % "base64". Default "hexId" + % converts to base 64 except for IDs + % which are converted to hexadecimals. + % "UseJsonName" - Whether to use JSON name of protobuf + % field to set the key of JSON + % "Timeout" - Maximum time above which exports + % will abort + % "HTTPHeaders" - Additional HTTP Headers + % + % See also OPENTELEMETRY.EXPORTERS.OTLP.OTLPGRPCLOGRECORDEXPORTER + arguments (Repeating) + optionnames (1,:) {mustBeTextScalar} + optionvalues + end + + obj = obj@opentelemetry.sdk.logs.LogRecordExporter(... + "libmexclass.opentelemetry.exporters.OtlpHttpLogRecordExporterProxy"); + + validnames = ["Endpoint", "Format", "JsonBytesMapping", ... + "UseJsonName", "Timeout", "HttpHeaders"]; + for i = 1:length(optionnames) + namei = validatestring(optionnames{i}, validnames); + valuei = optionvalues{i}; + obj.(namei) = valuei; + end + end + + function obj = set.Endpoint(obj, ep) + ep = obj.Validator.validateEndpoint(ep); + obj.Proxy.setEndpoint(ep); + obj.Endpoint = ep; + end + + function obj = set.Format(obj, newformat) + newformat = obj.Validator.validateFormat(newformat); + obj.Proxy.setFormat(newformat); + obj.Format = newformat; + end + + function obj = set.JsonBytesMapping(obj, jbm) + jbm = obj.Validator.validateJsonBytesMapping(jbm); + obj.Proxy.setJsonBytesMapping(jbm); + obj.JsonBytesMapping = jbm; + end + + function obj = set.UseJsonName(obj, ujn) + ujn = obj.Validator.validateUseJsonName(ujn); + obj.Proxy.setUseJsonName(ujn); + obj.UseJsonName = ujn; + end + + function obj = set.Timeout(obj, timeout) + obj.Validator.validateTimeout(timeout); + obj.Proxy.setTimeout(milliseconds(timeout)); + obj.Timeout = timeout; + end + + function obj = set.HttpHeaders(obj, httpheaders) + [headerkeys, headervalues] = obj.Validator.validateHttpHeaders(httpheaders); + obj.Proxy.setHttpHeaders(headerkeys, headervalues); + obj.HttpHeaders = httpheaders; + end + end +end diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/defaultLogRecordExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/defaultLogRecordExporter.m new file mode 100644 index 0000000..cb54b81 --- /dev/null +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/defaultLogRecordExporter.m @@ -0,0 +1,16 @@ +function dexp = defaultLogRecordExporter(varargin) +% Get the default log record exporter depending on installation +% EXP = OPENTELEMETRY.EXPORTERS.OTLP.DEFAULTSPANEXPORTER returns the +% default log record exporter. OtlpHttpLogRecordExporter is the default if it is +% installed. Otherwise, OtlpGrpcLogRecordExporter is the default. +% +% See also OPENTELEMETRY.EXPORTERS.OTLP.OTLPHTTPLOGRECORDEXPORTER, +% OPENTELEMETRY.EXPORTERS.OTLP.OTLPGRPCLOGRECORDEXPORTER + +% Copyright 2024 The MathWorks, Inc. + +if exist("opentelemetry.exporters.otlp.OtlpHttpLogRecordExporter", "class") + dexp = opentelemetry.exporters.otlp.OtlpHttpLogRecordExporter(varargin{:}); +else + dexp = opentelemetry.exporters.otlp.OtlpGrpcLogRecordExporter(varargin{:}); +end diff --git a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcLogRecordExporterProxy.h b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcLogRecordExporterProxy.h new file mode 100644 index 0000000..a99ddff --- /dev/null +++ b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpGrpcLogRecordExporterProxy.h @@ -0,0 +1,42 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h" + +#include "libmexclass/proxy/Proxy.h" +#include "libmexclass/proxy/method/Context.h" + +#include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_options.h" + +namespace logs_sdk = opentelemetry::sdk::logs; +namespace otlp_exporter = opentelemetry::exporter::otlp; + +namespace libmexclass::opentelemetry::exporters { +class OtlpGrpcLogRecordExporterProxy: public libmexclass::opentelemetry::sdk::LogRecordExporterProxy { + public: + OtlpGrpcLogRecordExporterProxy(otlp_exporter::OtlpGrpcLogRecordExporterOptions options) : CppOptions(options) { + REGISTER_METHOD(OtlpGrpcLogRecordExporterProxy, setEndpoint); + REGISTER_METHOD(OtlpGrpcLogRecordExporterProxy, setUseCredentials); + REGISTER_METHOD(OtlpGrpcLogRecordExporterProxy, setCertificatePath); + REGISTER_METHOD(OtlpGrpcLogRecordExporterProxy, setCertificateString); + REGISTER_METHOD(OtlpGrpcLogRecordExporterProxy, setTimeout); + REGISTER_METHOD(OtlpGrpcLogRecordExporterProxy, setHttpHeaders); + } + + static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); + + std::unique_ptr getInstance() override; + + void setEndpoint(libmexclass::proxy::method::Context& context); + void setUseCredentials(libmexclass::proxy::method::Context& context); + void setCertificatePath(libmexclass::proxy::method::Context& context); + void setCertificateString(libmexclass::proxy::method::Context& context); + void setTimeout(libmexclass::proxy::method::Context& context); + void setHttpHeaders(libmexclass::proxy::method::Context& context); + + private: + otlp_exporter::OtlpGrpcLogRecordExporterOptions CppOptions; +}; +} // namespace libmexclass::opentelemetry diff --git a/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpLogRecordExporterProxy.h b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpLogRecordExporterProxy.h new file mode 100644 index 0000000..90629c9 --- /dev/null +++ b/exporters/otlp/include/opentelemetry-matlab/exporters/otlp/OtlpHttpLogRecordExporterProxy.h @@ -0,0 +1,47 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h" + +#include "libmexclass/proxy/Proxy.h" +#include "libmexclass/proxy/method/Context.h" + +#include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/exporters/otlp/otlp_http_log_record_exporter_options.h" + +namespace logs_sdk = opentelemetry::sdk::logs; +namespace otlp_exporter = opentelemetry::exporter::otlp; + +namespace libmexclass::opentelemetry::exporters { +class OtlpHttpLogRecordExporterProxy: public libmexclass::opentelemetry::sdk::LogRecordExporterProxy { + public: + OtlpHttpLogRecordExporterProxy(otlp_exporter::OtlpHttpLogRecordExporterOptions options) : CppOptions(options) { + REGISTER_METHOD(OtlpHttpLogRecordExporterProxy, setEndpoint); + REGISTER_METHOD(OtlpHttpLogRecordExporterProxy, setFormat); + REGISTER_METHOD(OtlpHttpLogRecordExporterProxy, setJsonBytesMapping); + REGISTER_METHOD(OtlpHttpLogRecordExporterProxy, setUseJsonName); + REGISTER_METHOD(OtlpHttpLogRecordExporterProxy, setTimeout); + REGISTER_METHOD(OtlpHttpLogRecordExporterProxy, setHttpHeaders); + } + + static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); + + std::unique_ptr getInstance() override; + + void setEndpoint(libmexclass::proxy::method::Context& context); + + void setFormat(libmexclass::proxy::method::Context& context); + + void setJsonBytesMapping(libmexclass::proxy::method::Context& context); + + void setUseJsonName(libmexclass::proxy::method::Context& context); + + void setTimeout(libmexclass::proxy::method::Context& context); + + void setHttpHeaders(libmexclass::proxy::method::Context& context); + + private: + otlp_exporter::OtlpHttpLogRecordExporterOptions CppOptions; +}; +} // namespace libmexclass::opentelemetry diff --git a/exporters/otlp/src/OtlpGrpcLogRecordExporterProxy.cpp b/exporters/otlp/src/OtlpGrpcLogRecordExporterProxy.cpp new file mode 100644 index 0000000..3d2fb4a --- /dev/null +++ b/exporters/otlp/src/OtlpGrpcLogRecordExporterProxy.cpp @@ -0,0 +1,72 @@ +// Copyright 2024 The MathWorks, Inc. + +#include "opentelemetry-matlab/exporters/otlp/OtlpGrpcLogRecordExporterProxy.h" + +#include "libmexclass/proxy/ProxyManager.h" + +#include "opentelemetry/exporters/otlp/otlp_grpc_log_record_exporter_factory.h" + +namespace otlp_exporter = opentelemetry::exporter::otlp; + +namespace libmexclass::opentelemetry::exporters { +libmexclass::proxy::MakeResult OtlpGrpcLogRecordExporterProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { + otlp_exporter::OtlpGrpcLogRecordExporterOptions options; + return std::make_shared(options); +} + +std::unique_ptr OtlpGrpcLogRecordExporterProxy::getInstance() { + return otlp_exporter::OtlpGrpcLogRecordExporterFactory::Create(CppOptions); +} + +void OtlpGrpcLogRecordExporterProxy::setEndpoint(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray endpoint_mda = context.inputs[0]; + std::string endpoint = static_cast(endpoint_mda[0]); + + if (!endpoint.empty()) { + CppOptions.endpoint = endpoint; + } +} + + +void OtlpGrpcLogRecordExporterProxy::setUseCredentials(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray use_credentials_mda = context.inputs[0]; + CppOptions.use_ssl_credentials = use_credentials_mda[0]; +} + +void OtlpGrpcLogRecordExporterProxy::setCertificatePath(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray certpath_mda = context.inputs[0]; + std::string certpath = static_cast(certpath_mda[0]); + + if (!certpath.empty()) { + CppOptions.ssl_credentials_cacert_path = certpath; + } +} + +void OtlpGrpcLogRecordExporterProxy::setCertificateString(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray certstr_mda = context.inputs[0]; + std::string certstr = static_cast(certstr_mda[0]); + + if (!certstr.empty()) { + CppOptions.ssl_credentials_cacert_as_string = certstr; + } +} + +void OtlpGrpcLogRecordExporterProxy::setTimeout(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray timeout_mda = context.inputs[0]; + double timeout = timeout_mda[0]; + + if (timeout >= 0) { + CppOptions.timeout = std::chrono::milliseconds(static_cast(timeout)); + } +} + +void OtlpGrpcLogRecordExporterProxy::setHttpHeaders(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray headernames_mda = context.inputs[0]; + matlab::data::StringArray headervalues_mda = context.inputs[1]; + size_t nheaders = headernames_mda.getNumberOfElements(); + for (size_t i = 0; i < nheaders; ++i) { + CppOptions.metadata.insert(std::pair{static_cast(headernames_mda[i]), + static_cast(headervalues_mda[i])}); + } +} +} // namespace libmexclass::opentelemetry diff --git a/exporters/otlp/src/OtlpHttpLogRecordExporterProxy.cpp b/exporters/otlp/src/OtlpHttpLogRecordExporterProxy.cpp new file mode 100644 index 0000000..1ed1897 --- /dev/null +++ b/exporters/otlp/src/OtlpHttpLogRecordExporterProxy.cpp @@ -0,0 +1,77 @@ +// Copyright 2024 The MathWorks, Inc. + +#include "opentelemetry-matlab/exporters/otlp/OtlpHttpLogRecordExporterProxy.h" + +#include "libmexclass/proxy/ProxyManager.h" + +#include "opentelemetry/exporters/otlp/otlp_http_log_record_exporter_factory.h" + +namespace otlp_exporter = opentelemetry::exporter::otlp; + +namespace libmexclass::opentelemetry::exporters { +libmexclass::proxy::MakeResult OtlpHttpLogRecordExporterProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { + otlp_exporter::OtlpHttpLogRecordExporterOptions options; + return std::make_shared(options); +} + +std::unique_ptr OtlpHttpLogRecordExporterProxy::getInstance() { + return otlp_exporter::OtlpHttpLogRecordExporterFactory::Create(CppOptions); +} + +void OtlpHttpLogRecordExporterProxy::setEndpoint(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray endpoint_mda = context.inputs[0]; + std::string endpoint = static_cast(endpoint_mda[0]); + + if (!endpoint.empty()) { + CppOptions.url = endpoint; + } +} + +void OtlpHttpLogRecordExporterProxy::setFormat(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray dataformat_mda = context.inputs[0]; + std::string dataformat = static_cast(dataformat_mda[0]); + + if (dataformat.compare("JSON") == 0) { + CppOptions.content_type = otlp_exporter::HttpRequestContentType::kJson; + } else if (dataformat.compare("binary") == 0) { + CppOptions.content_type = otlp_exporter::HttpRequestContentType::kBinary; + } +} + +void OtlpHttpLogRecordExporterProxy::setJsonBytesMapping(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray json_bytes_mapping_mda = context.inputs[0]; + std::string json_bytes_mapping = static_cast(json_bytes_mapping_mda[0]); + + if (json_bytes_mapping.compare("hex") == 0) { + CppOptions.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kHex; + } else if (json_bytes_mapping.compare("hexId") == 0) { + CppOptions.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kHexId; + } else if (json_bytes_mapping.compare("base64") == 0) { + CppOptions.json_bytes_mapping = otlp_exporter::JsonBytesMappingKind::kBase64; + } +} + +void OtlpHttpLogRecordExporterProxy::setUseJsonName(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray use_json_name_mda = context.inputs[0]; + CppOptions.use_json_name = use_json_name_mda[0]; +} + +void OtlpHttpLogRecordExporterProxy::setTimeout(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray timeout_mda = context.inputs[0]; + double timeout = timeout_mda[0]; + + if (timeout >= 0) { + CppOptions.timeout = std::chrono::milliseconds(static_cast(timeout)); + } +} + +void OtlpHttpLogRecordExporterProxy::setHttpHeaders(libmexclass::proxy::method::Context& context) { + matlab::data::StringArray headernames_mda = context.inputs[0]; + matlab::data::StringArray headervalues_mda = context.inputs[1]; + size_t nheaders = headernames_mda.getNumberOfElements(); + for (size_t i = 0; i < nheaders; ++i) { + CppOptions.http_headers.insert(std::pair{static_cast(headernames_mda[i]), + static_cast(headervalues_mda[i])}); + } +} +} // namespace libmexclass::opentelemetry diff --git a/sdk/common/+opentelemetry/+sdk/+common/Cleanup.m b/sdk/common/+opentelemetry/+sdk/+common/Cleanup.m index c7fe244..e32ca60 100644 --- a/sdk/common/+opentelemetry/+sdk/+common/Cleanup.m +++ b/sdk/common/+opentelemetry/+sdk/+common/Cleanup.m @@ -1,22 +1,23 @@ classdef Cleanup -% Clean up methods for TracerProvider and MeterProvider +% Clean up methods for TracerProvider, MeterProvider, and LoggerProvider -% Copyright 2023 The MathWorks, Inc. +% Copyright 2023-2024 The MathWorks, Inc. methods (Static) function success = shutdown(p) % SHUTDOWN Shutdown % SUCCESS = SHUTDOWN(P) shuts down all processors/readers - % associated with P. P may be a tracer provider or a meter - % provider. Returns a logical that indicates whether - % shutdown was successful. + % associated with P. P may be a tracer provider, a meter + % provider, or a logger provider. Returns a logical that + % indicates whether shutdown was successful. % % See also FORCEFLUSH success = true; % return false if input is not the right type issdk = isa(p, "opentelemetry.sdk.trace.TracerProvider") || ... - isa(p, "opentelemetry.sdk.metrics.MeterProvider"); + isa(p, "opentelemetry.sdk.metrics.MeterProvider") || ... + isa(p, "opentelemetry.sdk.logs.LoggerProvider"); if issdk psdk = p; elseif isa(p, "opentelemetry.trace.TracerProvider") @@ -33,6 +34,13 @@ catch success = false; end + elseif isa(p, "opentelemetry.logs.LoggerProvider") + % convert to LoggerProvider class in sdk + try + psdk = opentelemetry.sdk.logs.LoggerProvider(p.Proxy); + catch + success = false; + end else success = false; end @@ -48,8 +56,8 @@ function success = forceFlush(p, timeout) % FORCEFLUSH Force flush - % SUCCESS = FORCEFLUSH(P) immediately exports all spans - % or metrics that have not yet been exported. Returns a + % SUCCESS = FORCEFLUSH(P) immediately exports all spans, + % metrics, or log records that have not yet been exported. Returns a % logical that indicates whether force flush was successful. % % SUCCESS = FORCEFLUSH(P, TIMEOUT) specifies a TIMEOUT @@ -62,7 +70,8 @@ % return false if input is not the right type if isa(p, "opentelemetry.sdk.trace.TracerProvider") || ... - isa(p, "opentelemetry.sdk.metrics.MeterProvider") + isa(p, "opentelemetry.sdk.metrics.MeterProvider") || ... + isa(p, "opentelemetry.sdk.logs.LoggerProvider") psdk = p; elseif isa(p, "opentelemetry.trace.TracerProvider") % convert to TracerProvider class in sdk @@ -78,6 +87,13 @@ catch success = false; end + elseif isa(p, "opentelemetry.logs.LoggerProvider") + % convert to LoggerProvider class in sdk + try + psdk = opentelemetry.sdk.logs.LoggerProvider(p.Proxy); + catch + success = false; + end else success = false; end diff --git a/sdk/logs/+opentelemetry/+sdk/+logs/BatchLogRecordProcessor.m b/sdk/logs/+opentelemetry/+sdk/+logs/BatchLogRecordProcessor.m new file mode 100644 index 0000000..dedda78 --- /dev/null +++ b/sdk/logs/+opentelemetry/+sdk/+logs/BatchLogRecordProcessor.m @@ -0,0 +1,89 @@ +classdef BatchLogRecordProcessor < opentelemetry.sdk.logs.LogRecordProcessor +% Batch log record processor creates batches of log records and passes them to an exporter. + +% Copyright 2024 The MathWorks, Inc. + + properties + MaximumQueueSize (1,1) double = 2048 % Maximum queue size. After queue size is reached, log records are dropped. + ScheduledDelay (1,1) duration = seconds(5) % Time interval between exports + MaximumExportBatchSize (1,1) double = 512 % Maximum batch size to export. + end + + methods + function obj = BatchLogRecordProcessor(exporter, optionnames, optionvalues) + % Batch log record processor creates batches of log records and passes them to an exporter. + % BLP = OPENTELEMETRY.SDK.LOGS.BATCHLOGRECORDPROCESSOR creates a + % batch log record processor that uses an OTLP HTTP exporter, which + % exports log records in OpenTelemetry Protocol (OTLP) format through HTTP. + % + % BLP = OPENTELEMETRY.SDK.LOGS.BATCHLOGRECORDPROCESSOR(EXP) specifies + % the log record exporter. Supported log record exporters are OTLP HTTP + % exporter and OTLP gRPC exporter. + % + % BLP = OPENTELEMETRY.SDK.LOGS.BATCHLOGRECORDPROCESSOR(EXP, PARAM1, + % VALUE1, PARAM2, VALUE2, ...) specifies optional parameter + % name/value pairs. Parameters are: + % "MaximumQueueSize" - Maximum queue size. After queue + % size is reached, log records are dropped. + % Default value is 2048. + % "ScheduledDelay" - Time interval between exports. + % Default interval is 5 seconds. + % "MaximumExportBatchSize" - Maximum batch size to export. + % Default size is 512. + % + % See also OPENTELEMETRY.SDK.LOGS.SIMPLELOGRECORDPROCESSOR, + % OPENTELEMETRY.EXPORTERS.OTLP.OTLPHTTPLOGRECORDEXPORTER, + % OPENTELEMETRY.EXPORTERS.OTLP.OTLPGRPCLOGRECORDEXPORTER, + % OPENTELEMETRY.SDK.LOGS.LOGGERPROVIDER + arguments + exporter {mustBeA(exporter, "opentelemetry.sdk.logs.LogRecordExporter")} = ... + opentelemetry.exporters.otlp.defaultLogRecordExporter() + end + arguments (Repeating) + optionnames (1,:) {mustBeTextScalar} + optionvalues + end + + obj = obj@opentelemetry.sdk.logs.LogRecordProcessor(exporter, ... + "libmexclass.opentelemetry.sdk.BatchLogRecordProcessorProxy"); + + validnames = ["MaximumQueueSize", "ScheduledDelay", "MaximumExportBatchSize"]; + for i = 1:length(optionnames) + namei = validatestring(optionnames{i}, validnames); + valuei = optionvalues{i}; + obj.(namei) = valuei; + end + end + + function obj = set.MaximumQueueSize(obj, maxqsz) + if ~isnumeric(maxqsz) || ~isscalar(maxqsz) || maxqsz <= 0 || ... + round(maxqsz) ~= maxqsz + error("opentelemetry:sdk:logs:BatchLogRecordProcessor:InvalidMaxQueueSize", ... + "MaximumQueueSize must be a scalar positive integer."); + end + maxqsz = double(maxqsz); + obj.Proxy.setMaximumQueueSize(maxqsz); + obj.MaximumQueueSize = maxqsz; + end + + function obj = set.ScheduledDelay(obj, delay) + if ~isduration(delay) || ~isscalar(delay) || delay <= 0 + error("opentelemetry:sdk:logs:BatchLogRecordProcessor:InvalidScheduledDelay", ... + "ScheduledDelay must be a positive duration scalar."); + end + obj.Proxy.setScheduledDelay(milliseconds(delay)); + obj.ScheduledDelay = delay; + end + + function obj = set.MaximumExportBatchSize(obj, maxbatch) + if ~isnumeric(maxbatch) || ~isscalar(maxbatch) || maxbatch <= 0 || ... + round(maxbatch) ~= maxbatch + error("opentelemetry:sdk:logs:BatchLogRecordProcessor:InvalidMaxExportBatchSize", ... + "MaximumExportBatchSize must be a scalar positive integer."); + end + maxbatch = double(maxbatch); + obj.Proxy.setMaximumExportBatchSize(maxbatch); + obj.MaximumExportBatchSize = maxbatch; + end + end +end diff --git a/sdk/logs/+opentelemetry/+sdk/+logs/LogRecordExporter.m b/sdk/logs/+opentelemetry/+sdk/+logs/LogRecordExporter.m new file mode 100644 index 0000000..1263905 --- /dev/null +++ b/sdk/logs/+opentelemetry/+sdk/+logs/LogRecordExporter.m @@ -0,0 +1,19 @@ +classdef LogRecordExporter +% Base class of log record exporters + +% Copyright 2024 The MathWorks, Inc. + + properties (GetAccess={?opentelemetry.sdk.logs.LogRecordProcessor, ... + ?opentelemetry.exporters.otlp.OtlpHttpLogRecordExporter, ... + ?opentelemetry.exporters.otlp.OtlpGrpcLogRecordExporter}) + Proxy % Proxy object to interface C++ code + end + + methods (Access=protected) + function obj = LogRecordExporter(proxyname, varargin) + % Base class constructor + obj.Proxy = libmexclass.proxy.Proxy("Name", proxyname, ...xx + "ConstructorArguments", varargin); + end + end +end diff --git a/sdk/logs/+opentelemetry/+sdk/+logs/LogRecordProcessor.m b/sdk/logs/+opentelemetry/+sdk/+logs/LogRecordProcessor.m new file mode 100644 index 0000000..68ab022 --- /dev/null +++ b/sdk/logs/+opentelemetry/+sdk/+logs/LogRecordProcessor.m @@ -0,0 +1,26 @@ +classdef LogRecordProcessor < matlab.mixin.Heterogeneous +% Base class of log record processors + +% Copyright 2024 The MathWorks, Inc. + + properties (GetAccess={?opentelemetry.sdk.logs.LoggerProvider,... + ?opentelemetry.sdk.logs.BatchLogRecordProcessor}) + Proxy % Proxy object to interface C++ code + end + + properties (SetAccess=immutable) + LogRecordExporter % Log record exporter object responsible for exporting telemetry data to an OpenTelemetry Collector or a compatible backend. + end + + methods (Access=protected) + function obj = LogRecordProcessor(exporter, proxyname, varargin) + % Base class constructor + + % Append SpanExporter proxy ID as the first input argument of + % proxy class constructor + obj.Proxy = libmexclass.proxy.Proxy("Name", proxyname, ... + "ConstructorArguments", [exporter.Proxy.ID varargin]); + obj.LogRecordExporter = exporter; + end + end +end diff --git a/sdk/logs/+opentelemetry/+sdk/+logs/LoggerProvider.m b/sdk/logs/+opentelemetry/+sdk/+logs/LoggerProvider.m new file mode 100644 index 0000000..43a45bc --- /dev/null +++ b/sdk/logs/+opentelemetry/+sdk/+logs/LoggerProvider.m @@ -0,0 +1,142 @@ +classdef LoggerProvider < opentelemetry.logs.LoggerProvider & handle + % An SDK implementation of logger provider, which stores a set of configurations used + % in a logging system. + + % Copyright 2024 The MathWorks, Inc. + + properties(Access=private) + isShutdown (1,1) logical = false + end + + properties (SetAccess=private) + LogRecordProcessor % Whether logs should be sent immediately or batched + Resource % Attributes attached to all logs + end + + methods + function obj = LoggerProvider(processor, optionnames, optionvalues) + % SDK implementation of logger provider + % LP = OPENTELEMETRY.SDK.LOGS.LOGGERPROVIDER creates a logger + % provider that uses a simple log record processor and default configurations. + % + % LP = OPENTELEMETRY.SDK.LOGS.LOGGERPROVIDER(P) uses log record + % processor P. P can be a simple or batched log record processor. + % + % LP = OPENTELEMETRY.SDK.LOGS.LOGGERPROVIDER(R, PARAM1, VALUE1, + % PARAM2, VALUE2, ...) specifies optional parameter name/value pairs. + % Parameters are: + % "Resource" - Additional resource attributes. + % Specified as a dictionary. + % + % See also OPENTELEMETRY.SDK.LOGS.SIMPLELOGRECORDPROCESSOR, + % OPENTELEMETRY.SDK.LOGS.BATCHLOGRECORDPROCESSOR + + arguments + processor {mustBeA(processor, ["opentelemetry.sdk.logs.LogRecordProcessor", ... + "libmexclass.proxy.Proxy"])} = ... + opentelemetry.sdk.logs.SimpleLogRecordProcessor() + end + arguments (Repeating) + optionnames (1,:) {mustBeTextScalar} + optionvalues + end + + % explicit call to superclass constructor to make it a no-op + obj@opentelemetry.logs.LoggerProvider("skip"); + + if isa(processor, "libmexclass.proxy.Proxy") + % This code branch is used to support conversion from API + % LoggerProvider to SDK equivalent, needed internally by + % opentelemetry.sdk.logs.Cleanup + lpproxy = processor; % rename the variable + assert(lpproxy.Name == "libmexclass.opentelemetry.LoggerProviderProxy"); + obj.Proxy = libmexclass.proxy.Proxy("Name", ... + "libmexclass.opentelemetry.sdk.LoggerProviderProxy", ... + "ConstructorArguments", {lpproxy.ID}); + % leave other properties unassigned, they won't be used + else + validnames = "Resource"; + resourcekeys = string.empty(); + resourcevalues = {}; + + resource = dictionary(resourcekeys, resourcevalues); + for i = 1:length(optionnames) + namei = validatestring(optionnames{i}, validnames); + valuei = optionvalues{i}; + if strcmp(namei, "Resource") + if ~isa(valuei, "dictionary") + error("opentelemetry:sdk:logs:LoggerProvider:InvalidResourceType", ... + "Resource input must be a dictionary."); + end + resource = valuei; + resourcekeys = keys(valuei); + resourcevalues = values(valuei,"cell"); + % collapse one level of cells, as this may be due to + % a behavior of dictionary.values + if all(cellfun(@iscell, resourcevalues)) + resourcevalues = [resourcevalues{:}]; + end + end + end + + obj.Proxy = libmexclass.proxy.Proxy("Name", ... + "libmexclass.opentelemetry.sdk.LoggerProviderProxy", ... + "ConstructorArguments", {processor.Proxy.ID, resourcekeys, ... + resourcevalues}); + obj.LogRecordProcessor = processor; + obj.Resource = resource; + end + end + + function addLogRecordProcessor(obj, processor) + % ADDLOGRECORDPROCESSOR Add an additional log record processor + % ADDLOGRECORDPROCESSOR(LP, P) adds an additional log record + % processor P to the list of log record processors used by + % logger provider LP. + % + % See also OPENTELEMETRY.SDK.LOGS.SIMPLELOGRECORDPROCESSOR, + % OPENTELEMETRY.SDK.LOGS.BATCHLOGRECORDPROCESSOR + arguments + obj + processor (1,1) {mustBeA(processor, "opentelemetry.sdk.logs.LogRecordProcessor")} + end + obj.Proxy.addProcessor(processor.Proxy.ID); + obj.LogRecordProcessor = [obj.LogRecordProcessor processor]; + end + + function success = shutdown(obj) + % SHUTDOWN Shutdown + % SUCCESS = SHUTDOWN(LP) shuts down all log record processors associated + % with logger provider LP and return a logical that indicates + % whether shutdown was successful. + % + % See also FORCEFLUSH + if ~obj.isShutdown + success = obj.Proxy.shutdown(); + obj.isShutdown = success; + else + success = true; + end + end + + function success = forceFlush(obj, timeout) + % FORCEFLUSH Force flush + % SUCCESS = FORCEFLUSH(LP) immediately exports all log + % records that have not yet been exported. Returns a logical + % that indicates whether force flush was successful. + % + % SUCCESS = FORCEFLUSH(LP, TIMEOUT) specifies a TIMEOUT + % duration. Force flush must be completed within this time, + % or else it will fail. + % + % See also SHUTDOWN + if obj.isShutdown + success = false; + elseif nargin < 2 || ~isa(timeout, "duration") % ignore timeout if not a duration + success = obj.Proxy.forceFlush(); + else + success = obj.Proxy.forceFlush(milliseconds(timeout)*1000); % convert to microseconds + end + end + end +end diff --git a/sdk/logs/+opentelemetry/+sdk/+logs/SimpleLogRecordProcessor.m b/sdk/logs/+opentelemetry/+sdk/+logs/SimpleLogRecordProcessor.m new file mode 100644 index 0000000..86f8011 --- /dev/null +++ b/sdk/logs/+opentelemetry/+sdk/+logs/SimpleLogRecordProcessor.m @@ -0,0 +1,30 @@ +classdef SimpleLogRecordProcessor < opentelemetry.sdk.logs.LogRecordProcessor +% Simple log record processor passes telemetry data to exporter as soon as they are generated. + +% Copyright 2024 The MathWorks, Inc. + + methods + function obj = SimpleLogRecordProcessor(exporter) + % Simple log record processor passes telemetry data to exporter as soon as they are generated. + % SLP = OPENTELEMETRY.SDK.LOGS.SIMPLELOGRECORDPROCESSOR creates + % a simple log record processor that uses an OTLP HTTP exporter, + % which exports log records in OpenTelemetry Protocol (OTLP) format through HTTP. + % + % SLP = OPENTELEMETRY.SDK.LOGS.SIMPLELOGRECORDPROCESSOR(EXP) specifies + % the log record exporter. Supported log record exporters are OTLP HTTP + % exporter and OTLP gRPC exporter. + % + % See also OPENTELEMETRY.SDK.LOGS.BATCHLOGRECORDPROCESSOR, + % OPENTELEMETRY.EXPORTERS.OTLP.OTLPHTTPLOGRECORDEXPORTER, + % OPENTELEMETRY.EXPORTERS.OTLP.OTLPGRPCLOGRECORDEXPORTER, + % OPENTELEMETRY.SDK.LOGS.LOGGERPROVIDER + arguments + exporter {mustBeA(exporter, "opentelemetry.sdk.logs.LogRecordExporter")} = ... + opentelemetry.exporters.otlp.defaultLogRecordExporter() + end + + obj = obj@opentelemetry.sdk.logs.LogRecordProcessor(exporter, ... + "libmexclass.opentelemetry.sdk.SimpleLogRecordProcessorProxy"); + end + end +end diff --git a/sdk/logs/include/opentelemetry-matlab/sdk/logs/BatchLogRecordProcessorProxy.h b/sdk/logs/include/opentelemetry-matlab/sdk/logs/BatchLogRecordProcessorProxy.h new file mode 100644 index 0000000..3b0038c --- /dev/null +++ b/sdk/logs/include/opentelemetry-matlab/sdk/logs/BatchLogRecordProcessorProxy.h @@ -0,0 +1,33 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "opentelemetry-matlab/sdk/logs/LogRecordProcessorProxy.h" + +#include "libmexclass/proxy/Proxy.h" +#include "libmexclass/proxy/method/Context.h" + +#include "opentelemetry/sdk/logs/processor.h" +#include "opentelemetry/sdk/logs/batch_log_record_processor_options.h" + +namespace logs_sdk = opentelemetry::sdk::logs; + +namespace libmexclass::opentelemetry::sdk { +class BatchLogRecordProcessorProxy : public LogRecordProcessorProxy { + public: + BatchLogRecordProcessorProxy(std::shared_ptr exporter); + + static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); + + std::unique_ptr getInstance() override; + + void setMaximumQueueSize(libmexclass::proxy::method::Context& context); + + void setScheduledDelay(libmexclass::proxy::method::Context& context); + + void setMaximumExportBatchSize(libmexclass::proxy::method::Context& context); + + private: + logs_sdk::BatchLogRecordProcessorOptions CppOptions; +}; +} // namespace libmexclass::opentelemetry diff --git a/sdk/logs/include/opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h b/sdk/logs/include/opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h new file mode 100644 index 0000000..95d3b6b --- /dev/null +++ b/sdk/logs/include/opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h @@ -0,0 +1,16 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "libmexclass/proxy/Proxy.h" + +#include "opentelemetry/sdk/logs/exporter.h" + +namespace logs_sdk = opentelemetry::sdk::logs; + +namespace libmexclass::opentelemetry::sdk { +class LogRecordExporterProxy : public libmexclass::proxy::Proxy { + public: + virtual std::unique_ptr getInstance() = 0; +}; +} // namespace libmexclass::opentelemetry diff --git a/sdk/logs/include/opentelemetry-matlab/sdk/logs/LogRecordProcessorProxy.h b/sdk/logs/include/opentelemetry-matlab/sdk/logs/LogRecordProcessorProxy.h new file mode 100644 index 0000000..b94ecea --- /dev/null +++ b/sdk/logs/include/opentelemetry-matlab/sdk/logs/LogRecordProcessorProxy.h @@ -0,0 +1,24 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h" + +#include "libmexclass/proxy/Proxy.h" +#include "libmexclass/proxy/ProxyManager.h" + +#include "opentelemetry/sdk/logs/processor.h" + +namespace logs_sdk = opentelemetry::sdk::logs; + +namespace libmexclass::opentelemetry::sdk { +class LogRecordProcessorProxy : public libmexclass::proxy::Proxy { + public: + virtual std::unique_ptr getInstance() = 0; + + protected: + LogRecordProcessorProxy(std::shared_ptr exporter) : LogRecordExporter(exporter) {} + + std::shared_ptr LogRecordExporter; +}; +} // namespace libmexclass::opentelemetry diff --git a/sdk/logs/include/opentelemetry-matlab/sdk/logs/LoggerProviderProxy.h b/sdk/logs/include/opentelemetry-matlab/sdk/logs/LoggerProviderProxy.h new file mode 100644 index 0000000..11b8f4f --- /dev/null +++ b/sdk/logs/include/opentelemetry-matlab/sdk/logs/LoggerProviderProxy.h @@ -0,0 +1,27 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "libmexclass/proxy/Proxy.h" +#include "libmexclass/proxy/method/Context.h" + +#include "opentelemetry-matlab/logs/LoggerProviderProxy.h" + +namespace libmexclass::opentelemetry::sdk { +class LoggerProviderProxy : public libmexclass::opentelemetry::LoggerProviderProxy { + public: + LoggerProviderProxy(nostd::shared_ptr lp) : libmexclass::opentelemetry::LoggerProviderProxy(lp) { + REGISTER_METHOD(LoggerProviderProxy, addProcessor); + REGISTER_METHOD(LoggerProviderProxy, shutdown); + REGISTER_METHOD(LoggerProviderProxy, forceFlush); + } + + static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); + + void addProcessor(libmexclass::proxy::method::Context& context); + + void shutdown(libmexclass::proxy::method::Context& context); + + void forceFlush(libmexclass::proxy::method::Context& context); +}; +} // namespace libmexclass::opentelemetry diff --git a/sdk/logs/include/opentelemetry-matlab/sdk/logs/SimpleLogRecordProcessorProxy.h b/sdk/logs/include/opentelemetry-matlab/sdk/logs/SimpleLogRecordProcessorProxy.h new file mode 100644 index 0000000..bfd2309 --- /dev/null +++ b/sdk/logs/include/opentelemetry-matlab/sdk/logs/SimpleLogRecordProcessorProxy.h @@ -0,0 +1,26 @@ +// Copyright 2024 The MathWorks, Inc. + +#pragma once + +#include "opentelemetry-matlab/sdk/logs/LogRecordProcessorProxy.h" +#include "opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h" + +#include "libmexclass/proxy/Proxy.h" + +#include "opentelemetry/sdk/logs/simple_log_record_processor_factory.h" + +namespace logs_sdk = opentelemetry::sdk::logs; + +namespace libmexclass::opentelemetry::sdk { +class SimpleLogRecordProcessorProxy : public LogRecordProcessorProxy { + public: + SimpleLogRecordProcessorProxy(std::shared_ptr exporter) + : LogRecordProcessorProxy(exporter) {} + + static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments); + + std::unique_ptr getInstance() override { + return logs_sdk::SimpleLogRecordProcessorFactory::Create(std::move(LogRecordExporter->getInstance())); + } +}; +} // namespace libmexclass::opentelemetry diff --git a/sdk/logs/src/BatchLogRecordProcessorProxy.cpp b/sdk/logs/src/BatchLogRecordProcessorProxy.cpp new file mode 100644 index 0000000..68cba86 --- /dev/null +++ b/sdk/logs/src/BatchLogRecordProcessorProxy.cpp @@ -0,0 +1,53 @@ +// Copyright 2024 The MathWorks, Inc. + +#include "opentelemetry-matlab/sdk/logs/BatchLogRecordProcessorProxy.h" +#include "opentelemetry-matlab/sdk/logs/LogRecordExporterProxy.h" + +#include "libmexclass/proxy/ProxyManager.h" + +#include "opentelemetry/sdk/logs/batch_log_record_processor_factory.h" + +namespace libmexclass::opentelemetry::sdk { +BatchLogRecordProcessorProxy::BatchLogRecordProcessorProxy(std::shared_ptr exporter) + : LogRecordProcessorProxy(exporter) { + REGISTER_METHOD(BatchLogRecordProcessorProxy, setMaximumQueueSize); + REGISTER_METHOD(BatchLogRecordProcessorProxy, setScheduledDelay); + REGISTER_METHOD(BatchLogRecordProcessorProxy, setMaximumExportBatchSize); +} + +libmexclass::proxy::MakeResult BatchLogRecordProcessorProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { + matlab::data::TypedArray exporterid_mda = constructor_arguments[0]; + libmexclass::proxy::ID exporterid = exporterid_mda[0]; + std::shared_ptr exporter = std::static_pointer_cast( + libmexclass::proxy::ProxyManager::getProxy(exporterid)); + return std::make_shared(exporter); +} + +std::unique_ptr BatchLogRecordProcessorProxy::getInstance() { + return logs_sdk::BatchLogRecordProcessorFactory::Create(std::move(LogRecordExporter->getInstance()), CppOptions); +} + +void BatchLogRecordProcessorProxy::setMaximumQueueSize(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray qsize_mda = context.inputs[0]; + double qsize = qsize_mda[0]; + if (qsize > 0) { + CppOptions.max_queue_size = static_cast(qsize); + } +} + +void BatchLogRecordProcessorProxy::setScheduledDelay(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray delay_mda = context.inputs[0]; + double delay = delay_mda[0]; + if (delay > 0) { + CppOptions.schedule_delay_millis = std::chrono::milliseconds(static_cast(delay)); + } +} + +void BatchLogRecordProcessorProxy::setMaximumExportBatchSize(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray batchsize_mda = context.inputs[0]; + double batchsize = batchsize_mda[0]; + if (batchsize > 0) { + CppOptions.max_export_batch_size = static_cast(batchsize); + } +} +} // namespace libmexclass::opentelemetry diff --git a/sdk/logs/src/LoggerProviderProxy.cpp b/sdk/logs/src/LoggerProviderProxy.cpp new file mode 100644 index 0000000..15748f2 --- /dev/null +++ b/sdk/logs/src/LoggerProviderProxy.cpp @@ -0,0 +1,82 @@ +// Copyright 2024 The MathWorks, Inc. + +#include "opentelemetry-matlab/sdk/logs/LoggerProviderProxy.h" +#include "opentelemetry-matlab/sdk/logs/LogRecordProcessorProxy.h" +#include "opentelemetry-matlab/sdk/common/resource.h" + +#include "libmexclass/proxy/ProxyManager.h" + +#include "opentelemetry/sdk/logs/logger_provider_factory.h" +#include "opentelemetry/sdk/logs/logger_provider.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/logs/logger_provider.h" +#include "opentelemetry/logs/noop.h" +#include "opentelemetry/common/key_value_iterable_view.h" + +namespace logs_api = opentelemetry::logs; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace nostd = opentelemetry::nostd; + +namespace libmexclass::opentelemetry::sdk { +libmexclass::proxy::MakeResult LoggerProviderProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { + libmexclass::proxy::MakeResult out; + if (constructor_arguments.getNumberOfElements() == 1) { + // if only one input, assume it is an API Logger Provider to support type conversion + matlab::data::TypedArray lpid_mda = constructor_arguments[0]; + libmexclass::proxy::ID lpid = lpid_mda[0]; + auto lp = std::static_pointer_cast( + libmexclass::proxy::ProxyManager::getProxy(lpid))->getInstance(); + // check if input can be cast to an SDK Logger Provider + auto lpsdk = dynamic_cast(lp.get()); + if (lpsdk == nullptr) { + return libmexclass::error::Error{"opentelemetry:sdk:logs:Cleanup:UnsetGlobalInstance", + "Clean up operations are not supported if global LoggerProvider instance is not set."}; + } + out = std::make_shared(nostd::shared_ptr(lp)); + } else { + matlab::data::TypedArray processorid_mda = constructor_arguments[0]; + libmexclass::proxy::ID processorid = processorid_mda[0]; + matlab::data::StringArray resourcenames_mda = constructor_arguments[1]; + size_t nresourceattrs = resourcenames_mda.getNumberOfElements(); + matlab::data::CellArray resourcevalues_mda = constructor_arguments[2]; + + auto processor = std::static_pointer_cast( + libmexclass::proxy::ProxyManager::getProxy(processorid))->getInstance(); + + auto resource_custom = createResource(resourcenames_mda, resourcevalues_mda); + + out = std::make_shared(nostd::shared_ptr( + std::move(logs_sdk::LoggerProviderFactory::Create(std::move(processor), resource_custom)))); + } + return out; +} + +void LoggerProviderProxy::addProcessor(libmexclass::proxy::method::Context& context) { + matlab::data::TypedArray processorid_mda = context.inputs[0]; + libmexclass::proxy::ID processorid = processorid_mda[0]; + + static_cast(*CppLoggerProvider).AddProcessor( + std::static_pointer_cast( + libmexclass::proxy::ProxyManager::getProxy(processorid))->getInstance()); +} + +void LoggerProviderProxy::shutdown(libmexclass::proxy::method::Context& context) { + matlab::data::ArrayFactory factory; + auto result_mda = factory.createScalar(static_cast(*CppLoggerProvider).Shutdown()); + context.outputs[0] = result_mda; + nostd::shared_ptr noop(new logs_api::NoopLoggerProvider); + CppLoggerProvider.swap(noop); +} + +void LoggerProviderProxy::forceFlush(libmexclass::proxy::method::Context& context) { + matlab::data::ArrayFactory factory; + + if (context.inputs.getNumberOfElements() == 0) { + context.outputs[0] = factory.createScalar(static_cast(*CppLoggerProvider).ForceFlush()); + } else { // number of inputs > 0 + matlab::data::TypedArray timeout_mda = context.inputs[0]; + auto timeout = std::chrono::microseconds(timeout_mda[0]); + context.outputs[0] = factory.createScalar(static_cast(*CppLoggerProvider).ForceFlush(timeout)); + } +} +} // namespace libmexclass::opentelemetry diff --git a/sdk/logs/src/SimpleLogRecordProcessorProxy.cpp b/sdk/logs/src/SimpleLogRecordProcessorProxy.cpp new file mode 100644 index 0000000..12f7852 --- /dev/null +++ b/sdk/logs/src/SimpleLogRecordProcessorProxy.cpp @@ -0,0 +1,14 @@ +// Copyright 2024 The MathWorks, Inc. + +#include "opentelemetry-matlab/sdk/logs/SimpleLogRecordProcessorProxy.h" + +namespace libmexclass::opentelemetry::sdk { +libmexclass::proxy::MakeResult SimpleLogRecordProcessorProxy::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) { + matlab::data::TypedArray exporterid_mda = constructor_arguments[0]; + libmexclass::proxy::ID exporterid = exporterid_mda[0]; + std::shared_ptr exporter = std::static_pointer_cast( + libmexclass::proxy::ProxyManager::getProxy(exporterid)); + return std::make_shared(exporter); +} +} // namespace libmexclass::opentelemetry + diff --git a/test/tlogs.m b/test/tlogs.m new file mode 100644 index 0000000..e762272 --- /dev/null +++ b/test/tlogs.m @@ -0,0 +1,352 @@ +classdef tlogs < matlab.unittest.TestCase + % tests for logs + + % Copyright 2024 The MathWorks, Inc. + + properties + OtelConfigFile + JsonFile + PidFile + OtelcolName + Otelcol + ListPid + ReadPidList + ExtractPid + Sigint + Sigterm + end + + methods (TestClassSetup) + function setupOnce(testCase) + % add the utils folder to the path + utilsfolder = fullfile(fileparts(mfilename('fullpath')), "utils"); + testCase.applyFixture(matlab.unittest.fixtures.PathFixture(utilsfolder)); + commonSetupOnce(testCase); + end + end + + methods (TestMethodSetup) + function setup(testCase) + commonSetup(testCase); + end + end + + methods (TestMethodTeardown) + function teardown(testCase) + commonTeardown(testCase); + end + end + + methods (Test) + function testBasic(testCase) + % testBasic: emitLogRecord with minimal inputs + loggername = "foo"; + logseverity = "debug"; + logmessage = "bar"; + + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, loggername); + emitLogRecord(lg, logseverity, logmessage); + expectedtimestamp = datetime("now", "TimeZone", "UTC"); + + % verify object properties + verifyEqual(testCase, lg.Name, loggername); + verifyEqual(testCase, lg.Version, ""); + verifyEqual(testCase, lg.Schema, ""); + + % perform test comparisons + results = readJsonResults(testCase); + verifyNumElements(testCase, results, 1); + if ~isempty(results) + results = results{1}; + + % check logger name, log content and severity, trace and span IDs + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.scope.name), loggername); + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.severityText), upper(logseverity)); + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.body.stringValue), logmessage); + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.traceId), ""); + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.spanId), ""); + + % check timestamp + % use a tolerance when testing times + tol = seconds(4); + verifyLessThanOrEqual(testCase, abs(datetime(double(string(... + results.resourceLogs.scopeLogs.logRecords.observedTimeUnixNano))/1e9, ... + "convertFrom", "posixtime", "TimeZone", "UTC") - expectedtimestamp), tol); + + % check resource + resourcekeys = string({results.resourceLogs.resource.attributes.key}); + languageidx = find(resourcekeys == "telemetry.sdk.language"); + verifyNotEmpty(testCase, languageidx); + verifyEqual(testCase, results.resourceLogs.resource.attributes(languageidx).value.stringValue, 'MATLAB'); + + versionidx = find(resourcekeys == "telemetry.sdk.version"); + verifyNotEmpty(testCase, versionidx); + versionstr = strip(fileread(fullfile("..", "VERSION.txt"))); + verifyEqual(testCase, results.resourceLogs.resource.attributes(versionidx).value.stringValue, versionstr); + + nameidx = find(resourcekeys == "telemetry.sdk.name"); + verifyNotEmpty(testCase, nameidx); + verifyEqual(testCase, results.resourceLogs.resource.attributes(nameidx).value.stringValue, 'opentelemetry'); + + serviceidx = find(resourcekeys == "service.name"); + verifyNotEmpty(testCase, serviceidx); + verifyEqual(testCase, results.resourceLogs.resource.attributes(serviceidx).value.stringValue, 'unknown_service'); + end + end + + function testImplicitContext(testCase) + % testImplicitContext: Test current trace and span IDs are recorded + + % start a span and make it current + tp = opentelemetry.sdk.trace.TracerProvider(); + tr = getTracer(tp, "foo"); + sp = startSpan(tr, "bar"); + sc = makeCurrent(sp); %#ok<*NASGU> + + % emit a log record + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, "baz"); + emitLogRecord(lg, "info", "qux"); + + sc = []; + endSpan(sp); + + % perform test comparisons + results = readJsonResults(testCase); + + % check for two records: first a log and second a span + verifyNumElements(testCase, results, 2); + verifyTrue(testCase, isfield(results{1}, "resourceLogs")); + verifyTrue(testCase, isfield(results{2}, "resourceSpans")); + log = results{1}; + span = results{2}; + + % check the trace and span IDs in the log record match those of + % the span + verifyEqual(testCase, log.resourceLogs.scopeLogs.logRecords.traceId, ... + span.resourceSpans.scopeSpans.spans.traceId); + verifyEqual(testCase, log.resourceLogs.scopeLogs.logRecords.spanId, ... + span.resourceSpans.scopeSpans.spans.spanId); + end + + function testExplicitContext(testCase) + % testExplicitContext: Explicitly specifying a context + + % start a span + tp = opentelemetry.sdk.trace.TracerProvider(); + tr = getTracer(tp, "foo"); + sp = startSpan(tr, "bar"); + spctxt = getSpanContext(sp); + traceid = spctxt.TraceId; + spanid = spctxt.SpanId; + + context = opentelemetry.context.Context; + context = opentelemetry.trace.Context.insertSpan(context, sp); + + % emit a log record and specify context + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, "baz"); + emitLogRecord(lg, "info", "qux", Context=context); + endSpan(sp); + + % perform test comparisons + results = readJsonResults(testCase); + log = results{1}; + + % check the record trace and span IDs in the log record matches + % those of the span + verifyEqual(testCase, string(log.resourceLogs.scopeLogs.logRecords.traceId), ... + traceid); + verifyEqual(testCase, string(log.resourceLogs.scopeLogs.logRecords.spanId), ... + spanid); + end + + + function testTimestamp(testCase) + % testTimestamp: specifying a timestamp + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, "foo"); + timestamp = datetime(2020,3,12,9,45,0); + emitLogRecord(lg, "info", "bar", "Timestamp", timestamp); + + % perform test comparisons + results = readJsonResults(testCase); + verifyEqual(testCase, datetime(double(string(... + results{1}.resourceLogs.scopeLogs.logRecords.timeUnixNano))/1e9, ... + "convertFrom", "posixtime"), timestamp); % convert from nanoseconds to seconds + end + + function testAttributes(testCase) + % testAttributes: specifying attributes + + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, "foo"); + attributes = dictionary(["stringscalar", "doublescalar", "int32scalar", "uint32scalar", ... + "int64scalar", "logicalscalar", "doublearray", "int32array", "uint32array", ... + "int64array", "logicalarray", "stringarray"], {"foo", 10, int32(10), uint32(20), ... + int64(35), false, [2 3; 4 5], int32(1:6), uint32((15:18).'), int64(reshape(1:4,2,1,2)), ... + [true false true], ["foo", "bar", "quux", "quz"]}); + emitLogRecord(lg, "warn", "bar", Attributes=attributes); + + % perform test comparisons + results = readJsonResults(testCase); + + attrkeys = string({results{1}.resourceLogs.scopeLogs.logRecords.attributes.key}); + + % scalars + stringscidx = find(attrkeys == "stringscalar"); + verifyNotEmpty(testCase, stringscidx); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringscidx).value.stringValue), attributes{"stringscalar"}); + + doublescidx = find(attrkeys == "doublescalar"); + verifyNotEmpty(testCase, doublescidx); + verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(doublescidx).value.doubleValue, ... + attributes{"doublescalar"}); + + i32scidx = find(attrkeys == "int32scalar"); + verifyNotEmpty(testCase, i32scidx); + verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(i32scidx).value.intValue, ... + char(string(attributes{"int32scalar"}))); + + u32scidx = find(attrkeys == "uint32scalar"); + verifyNotEmpty(testCase, u32scidx); + verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(u32scidx).value.intValue, ... + char(string(attributes{"uint32scalar"}))); + + i64scidx = find(attrkeys == "int64scalar"); + verifyNotEmpty(testCase, i64scidx); + verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(i64scidx).value.intValue, ... + char(string(attributes{"int64scalar"}))); + + logicalscidx = find(attrkeys == "logicalscalar"); + verifyNotEmpty(testCase, logicalscidx); + verifyFalse(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(logicalscidx).value.boolValue); + + % arrays + doublearidx = find(attrkeys == "doublearray"); + verifyNotEmpty(testCase, doublearidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(doublearidx).value.arrayValue.values.doubleValue], ... + reshape(attributes{"doublearray"}, 1, [])); + + doubleszidx = find(attrkeys == "doublearray.size"); + verifyNotEmpty(testCase, doubleszidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(doubleszidx).value.arrayValue.values.doubleValue], ... + size(attributes{"doublearray"})); + + i32aridx = find(attrkeys == "int32array"); + verifyNotEmpty(testCase, i32aridx); + verifyEqual(testCase, double(string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(i32aridx).value.arrayValue.values.intValue})), ... + double(reshape(attributes{"int32array"},1,[]))); + + i32szidx = find(attrkeys == "int32array.size"); + verifyNotEmpty(testCase, i32szidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(i32szidx).value.arrayValue.values.doubleValue], ... + size(attributes{"int32array"})); + + u32aridx = find(attrkeys == "uint32array"); + verifyNotEmpty(testCase, u32aridx); + verifyEqual(testCase, double(string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(u32aridx).value.arrayValue.values.intValue})), ... + double(reshape(attributes{"uint32array"},1,[]))); + + u32szidx = find(attrkeys == "uint32array.size"); + verifyNotEmpty(testCase, u32szidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(u32szidx).value.arrayValue.values.doubleValue], ... + size(attributes{"uint32array"})); + + i64aridx = find(attrkeys == "int64array"); + verifyNotEmpty(testCase, i64aridx); + verifyEqual(testCase, double(string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(i64aridx).value.arrayValue.values.intValue})), ... + double(reshape(attributes{"int64array"},1,[]))); + + i64szidx = find(attrkeys == "int64array.size"); + verifyNotEmpty(testCase, i64szidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(i64szidx).value.arrayValue.values.doubleValue], ... + size(attributes{"int64array"})); + + logicalaridx = find(attrkeys == "logicalarray"); + verifyNotEmpty(testCase, logicalaridx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(logicalaridx).value.arrayValue.values.boolValue], ... + reshape(attributes{"logicalarray"},1,[])); + + logicalszidx = find(attrkeys == "logicalarray.size"); + verifyNotEmpty(testCase, logicalszidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(logicalszidx).value.arrayValue.values.doubleValue], ... + size(attributes{"logicalarray"})); + + stringaridx = find(attrkeys == "stringarray"); + verifyNotEmpty(testCase, stringaridx); + verifyEqual(testCase, string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringaridx).value.arrayValue.values.stringValue}), ... + attributes{"stringarray"}); + + stringszidx = find(attrkeys == "stringarray.size"); + verifyNotEmpty(testCase, stringszidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringszidx).value.arrayValue.values.doubleValue], ... + size(attributes{"stringarray"})); + end + + function testSeverityFunctions(testCase) + % testSeverityFunctions: trace, debug, info, warn, error, fatal + loggername = "foo"; + logmessage = "bar"; + + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, loggername); + + funcs = {@lg.trace, @lg.debug, @lg.info, @lg.warn, @lg.error, @lg.fatal}; + nfuncs = length(funcs); + logseverity = ["trace" "debug" "info" "warn" "error" "fatal"]; + + for i = 1:nfuncs + funcs{i}(logmessage); + end + + % perform test comparisons + results = readJsonResults(testCase); + verifyNumElements(testCase, results, nfuncs); + for i = 1:nfuncs + resultsi = results{i}; + + % check logger name, log content and severity, trace and span IDs + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.scope.name), loggername); + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.severityText), upper(logseverity(i))); + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.body.stringValue), logmessage); + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.traceId), ""); + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.spanId), ""); + end + end + + function testGetSetLoggerProvider(testCase) + % testGetSetLoggerProvider: setting and getting global instance of LoggerProvider + customkey = "quux"; + customvalue = 1; + proc = opentelemetry.sdk.logs.SimpleLogRecordProcessor; + lp = opentelemetry.sdk.logs.LoggerProvider(proc, ... + "Resource", dictionary(customkey, customvalue)); % specify an arbitrary resource as an identifier + setLoggerProvider(lp); + clear("lp"); + + loggername = "foo"; + logseverity = "warn"; + logmessage = "bar"; + lg = opentelemetry.logs.getLogger(loggername); + emitLogRecord(lg, logseverity, logmessage); + + % perform test comparisons + results = readJsonResults(testCase); + + % check log record, and check its resource to identify the + % correct LoggerProvider has been used + verifyNotEmpty(testCase, results); + + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.severityText), upper(logseverity)); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logmessage); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.scope.name), loggername); + + resourcekeys = string({results{1}.resourceLogs.resource.attributes.key}); + idx = find(resourcekeys == customkey); + verifyNotEmpty(testCase, idx); + verifyEqual(testCase, results{1}.resourceLogs.resource.attributes(idx).value.doubleValue, customvalue); + end + end +end \ No newline at end of file diff --git a/test/tlogs_sdk.m b/test/tlogs_sdk.m new file mode 100644 index 0000000..accc6c4 --- /dev/null +++ b/test/tlogs_sdk.m @@ -0,0 +1,187 @@ +classdef tlogs_sdk < matlab.unittest.TestCase + % tests for logging SDK (log record processors, exporters, resource) + + % Copyright 2024 The MathWorks, Inc. + + properties + OtelConfigFile + JsonFile + PidFile + OtelcolName + Otelcol + ListPid + ReadPidList + ExtractPid + Sigint + Sigterm + end + + methods (TestClassSetup) + function setupOnce(testCase) + % add the utils folder to the path + utilsfolder = fullfile(fileparts(mfilename('fullpath')), "utils"); + testCase.applyFixture(matlab.unittest.fixtures.PathFixture(utilsfolder)); + commonSetupOnce(testCase); + end + end + + methods (TestMethodSetup) + function setup(testCase) + commonSetup(testCase); + end + end + + methods (TestMethodTeardown) + function teardown(testCase) + commonTeardown(testCase); + end + end + + methods (Test) + function testAddLogRecordProcessor(testCase) + % testAddLogRecordProcessor: addLogRecordProcessor method + loggername = "foo"; + logcontent = "bar"; + processor1 = opentelemetry.sdk.logs.SimpleLogRecordProcessor; + processor2 = opentelemetry.sdk.logs.SimpleLogRecordProcessor; + p = opentelemetry.sdk.logs.LoggerProvider(processor1); + p.addLogRecordProcessor(processor2); + lg = p.getLogger(loggername); + lg.emitLogRecord("debug", logcontent); + + % verify if the provider has two log processors attached + processor_count = numel(p.LogRecordProcessor); + verifyEqual(testCase,processor_count, 2); + + % verify if the json results has two exported instances after + % emitting a single log record + results = readJsonResults(testCase); + result_count = numel(results); + verifyEqual(testCase,result_count, 2); + end + + function testBatchLogRecordProcessor(testCase) + % testBatchLogRecordProcessor: setting properties of + % BatchRecordProcessor + loggername = "foo"; + logseverity = "debug"; + logcontent = "bar"; + queuesize = 500; + delay = seconds(2); + batchsize = 50; + b = opentelemetry.sdk.logs.BatchLogRecordProcessor; + b.MaximumQueueSize = queuesize; + b.ScheduledDelay = delay; + b.MaximumExportBatchSize = batchsize; + + % verify properties modified successfully + verifyEqual(testCase, b.MaximumQueueSize, queuesize); + verifyEqual(testCase, b.ScheduledDelay, delay); + verifyEqual(testCase, b.MaximumExportBatchSize, batchsize) + verifyEqual(testCase, class(b.LogRecordExporter), ... + class(opentelemetry.exporters.otlp.defaultLogRecordExporter)); + + p = opentelemetry.sdk.logs.LoggerProvider(b); + lg = p.getLogger(loggername); + lg.emitLogRecord(logseverity, logcontent); + % flush the record + forceFlush(p, seconds(2)); + + % verify log content and severity + results = readJsonResults(testCase); + results = results{1}; + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.scope.name), loggername); + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.severityText), upper(logseverity)); + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.body.stringValue), logcontent); + end + + function testCustomResource(testCase) + % testCustomResource: check custom resources are included in + % emitted log record + customkeys = ["foo" "bar"]; + customvalues = [1 5]; + lp = opentelemetry.sdk.logs.LoggerProvider(opentelemetry.sdk.logs.SimpleLogRecordProcessor, ... + "Resource", dictionary(customkeys, customvalues)); + lg = getLogger(lp, "baz"); + emitLogRecord(lg, "debug", "qux"); + + % perform test comparisons + results = readJsonResults(testCase); + results = results{1}; + + resourcekeys = string({results.resourceLogs.resource.attributes.key}); + for i = length(customkeys) + idx = find(resourcekeys == customkeys(i)); + verifyNotEmpty(testCase, idx); + verifyEqual(testCase, results.resourceLogs.resource.attributes(idx).value.doubleValue, customvalues(i)); + end + end + + function testShutdown(testCase) + % testShutdown: shutdown method should stop exporting + % of log records + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, "foo"); + + % emit a log record + logcontent = "bar"; + emitLogRecord(lg, "info", logcontent); + + % shutdown the logger provider + verifyTrue(testCase, shutdown(lp)); + + % emit another log record + emitLogRecord(lg, "info", "quux"); + + % verify only the first log record was emitted + results = readJsonResults(testCase); + verifyNumElements(testCase, results, 1); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logcontent); + end + + function testCleanupSdk(testCase) + % testCleanupSdk: shutdown an SDK logger provider through the Cleanup class + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, "foo"); + + % emit a log record + logcontent = "bar"; + emitLogRecord(lg, "warn", logcontent); + + % shutdown the SDK logger provider through the Cleanup class + verifyTrue(testCase, opentelemetry.sdk.common.Cleanup.shutdown(lp)); + + % emit another log record + emitLogRecord(lg, "warn", "quux"); + + % verify only the first log record was recorded + results = readJsonResults(testCase); + verifyNumElements(testCase, results, 1); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logcontent); + end + + function testCleanupApi(testCase) + % testCleanupApi: shutdown an API logger provider through the Cleanup class + lp = opentelemetry.sdk.logs.LoggerProvider(); + setLoggerProvider(lp); + clear("lp"); + lp_api = opentelemetry.logs.Provider.getLoggerProvider(); + lg = getLogger(lp_api, "foo"); + + % emit a log record + logcontent = "bar"; + emitLogRecord(lg, "error", logcontent); + + % shutdown the API logger provider through the Cleanup class + verifyTrue(testCase, opentelemetry.sdk.common.Cleanup.shutdown(lp_api)); + + % emit another log record + emitLogRecord(lg, "error", "quux"); + + % verify only the first log record was recorded + results = readJsonResults(testCase); + verifyNumElements(testCase, results, 1); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logcontent); + end + end +end \ No newline at end of file From 5623a7b4ec55f93c88019d9e0b1b496ca421c135 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Tue, 9 Apr 2024 11:33:55 -0400 Subject: [PATCH 02/18] Update README and add a test about log severity --- README.md | 26 ++++++++++++++++++----- api/logs/+opentelemetry/+logs/Logger.m | 5 +++++ test/tlogs.m | 29 ++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 67a73c8..cd053b8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ MATLAB® interface to [OpenTelemetry™](https://opentelemetry.io/), based on the [OpenTelemetry Specification](https://opentelemetry.io/docs/specs/otel/). OpenTelemetry is an observability framework for creating and managing telemetry data, such as traces, metrics, and logs. This data can then be sent to an observability back-end for monitoring, alerts, and analysis. ### Status -- Tracing and metrics are fully supported. Logs will be in the future. +- Tracing, metrics, and logs are all fully supported. - Supported and tested on Windows®, Linux®, and macOS. ### MathWorks Products (https://www.mathworks.com) @@ -58,8 +58,8 @@ otelcol --config ``` 2. Start a span ``` ->> tr = opentelemetry.trace.getTracer("First Tracer"); ->> sp = tr.startSpan("First Span"); +>> tr = opentelemetry.trace.getTracer("My Tracer"); +>> sp = tr.startSpan("My Span"); ``` 3. End the span ``` @@ -74,8 +74,8 @@ otelcol --config ``` 2. Create a counter ``` ->> m = opentelemetry.metrics.getMeter("First Meter"); ->> c = m.createCounter("FirstCounter"); +>> m = opentelemetry.metrics.getMeter("My Meter"); +>> c = m.createCounter("My Counter"); ``` 3. Increment the counter ``` @@ -83,6 +83,22 @@ otelcol --config ``` 4. If your collector is configured to display the data, you should see your counter displayed after 1 minute. +### Logs +1. Create a default logger provider and save it. +``` +>> p = opentelemetry.sdk.logs.LoggerProvider(); +>> setLoggerProvider(p); +``` +2. Create a logger +``` +>> l = opentelemetry.logs.getLogger("My Logger"); +``` +3. Emit a log record with "info" level +``` +>> info(l, "My Message"); +``` +4. If your collector is configured to display the data, you should see your log record displayed. + For more examples, see the "examples" folder. ## Help diff --git a/api/logs/+opentelemetry/+logs/Logger.m b/api/logs/+opentelemetry/+logs/Logger.m index 150bba0..6520c79 100644 --- a/api/logs/+opentelemetry/+logs/Logger.m +++ b/api/logs/+opentelemetry/+logs/Logger.m @@ -55,11 +55,15 @@ function emitLogRecord(obj, severity, content, trailingnames, trailingvalues) trailingvalues end if isnumeric(severity) && isscalar(severity) + % severity number if severity < 1 || severity > 24 || round(severity) ~= severity severity = 0; % invalid end elseif (isstring(severity) && isscalar(severity)) || ... (ischar(severity) && isrow(severity)) + % severity text + % Support 24 valid severity levels: trace, trace2, trace3, + % trace4, debug, debug2, debug3, ... severitylist = ["trace", "debug", "info", "warn", "error", "fatal"]; severitylist = reshape(severitylist + [""; "2"; "3"; "4"], 1, []); d = dictionary(severitylist, 1:length(severitylist)); @@ -69,6 +73,7 @@ function emitLogRecord(obj, severity, content, trailingnames, trailingvalues) severity = 0; % invalid end else + % invalid severity severity = 0; end try diff --git a/test/tlogs.m b/test/tlogs.m index e762272..34ffc15 100644 --- a/test/tlogs.m +++ b/test/tlogs.m @@ -95,6 +95,35 @@ function testBasic(testCase) end end + function testSeverity(testCase) + % testSeverity: different ways of setting severity + loggername = "foo"; + logmessage = "bar"; + + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, loggername); + + % set severity using text, number, and text with trailing + % integer + logseverity = {"info" 7 "warn2"}; + logseveritytext = ["info" "debug3" "warn2"]; + nseverity = length(logseverity); + + for i = 1:nseverity + emitLogRecord(lg, logseverity{i}, logmessage); + end + + % perform test comparisons + results = readJsonResults(testCase); + verifyNumElements(testCase, results, nseverity); + for i = 1:nseverity + resultsi = results{i}; + + % check severity is set correctly + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.severityText), upper(logseveritytext(i))); + end + end + function testImplicitContext(testCase) % testImplicitContext: Test current trace and span IDs are recorded From a37259fce38f159e180ce592d28b6b41db046122 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Wed, 10 Apr 2024 12:43:32 -0400 Subject: [PATCH 03/18] allow non-string log content --- api/common/src/attribute.cpp | 10 +++-- api/logs/+opentelemetry/+logs/Logger.m | 8 ++-- api/logs/src/LoggerProxy.cpp | 23 +++++++--- test/tlogs.m | 59 ++++++++++++++++++++++++++ test/tlogs_sdk.m | 10 ++++- 5 files changed, 94 insertions(+), 16 deletions(-) diff --git a/api/common/src/attribute.cpp b/api/common/src/attribute.cpp index f21970f..5fa85d6 100644 --- a/api/common/src/attribute.cpp +++ b/api/common/src/attribute.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 The MathWorks, Inc. +// Copyright 2023-2024 The MathWorks, Inc. #include "opentelemetry-matlab/common/attribute.h" @@ -34,10 +34,12 @@ void processAttribute(const std::string& attrname, // input, attribute name } else if (valtype == matlab::data::ArrayType::LOGICAL) { matlab::data::TypedArray attrvalue_mda = attrvalue; attrs.Attributes.push_back(std::pair(attrname, attrvalue_mda[0])); - } else { // string + } else if (valtype == matlab::data::ArrayType::MATLAB_STRING) { // string matlab::data::StringArray attrvalue_mda = attrvalue; attrs.StringBuffer.push_back(static_cast(*(attrvalue_mda.begin()))); attrs.Attributes.push_back(std::pair(attrname, attrs.StringBuffer.back())); + } else { // ignore all other types + return; } } else { // array case if (valtype == matlab::data::ArrayType::DOUBLE) { @@ -60,7 +62,7 @@ void processAttribute(const std::string& attrname, // input, attribute name matlab::data::TypedArray attrvalue_mda = attrvalue; attrs.Attributes.push_back(std::pair(attrname, nostd::span{&(*attrvalue_mda.cbegin()), &(*attrvalue_mda.cend())})); - } else { // string + } else if (valtype == matlab::data::ArrayType::MATLAB_STRING) { // string matlab::data::StringArray attrvalue_mda = attrvalue; std::vector strarray_attr; strarray_attr.reserve(nelements); @@ -72,6 +74,8 @@ void processAttribute(const std::string& attrname, // input, attribute name attrs.StringViewBuffer.push_back(strarray_attr); attrs.Attributes.push_back(std::pair(attrname, nostd::span{&(*attrs.StringViewBuffer.back().cbegin()), &(*attrs.StringViewBuffer.back().cend())})); + } else { // ignore all other types + return; } // Add a size attribute to preserve the shape std::string sizeattr{attrname + ".size"}; diff --git a/api/logs/+opentelemetry/+logs/Logger.m b/api/logs/+opentelemetry/+logs/Logger.m index 6520c79..823824d 100644 --- a/api/logs/+opentelemetry/+logs/Logger.m +++ b/api/logs/+opentelemetry/+logs/Logger.m @@ -76,11 +76,9 @@ function emitLogRecord(obj, severity, content, trailingnames, trailingvalues) % invalid severity severity = 0; end - try - content = string(content); - catch - content = ""; - end + + % content + content = convertCharsToStrings(content); % force char rows into strings % validate the trailing names and values optionnames = ["Context", "Timestamp", "Attributes"]; diff --git a/api/logs/src/LoggerProxy.cpp b/api/logs/src/LoggerProxy.cpp index 71308c9..95c4889 100644 --- a/api/logs/src/LoggerProxy.cpp +++ b/api/logs/src/LoggerProxy.cpp @@ -12,8 +12,6 @@ #include "MatlabDataArray.hpp" -//#include - namespace logs_api = opentelemetry::logs; namespace trace_api = opentelemetry::trace; namespace context_api = opentelemetry::context; @@ -24,8 +22,7 @@ namespace libmexclass::opentelemetry { void LoggerProxy::emitLogRecord(libmexclass::proxy::method::Context& context) { matlab::data::TypedArray severity_mda = context.inputs[0]; int severity = static_cast(severity_mda[0]); - matlab::data::StringArray content_mda = context.inputs[1]; - std::string content = static_cast(content_mda[0]); + matlab::data::Array content_mda = context.inputs[1]; matlab::data::TypedArray contextid_mda = context.inputs[2]; libmexclass::proxy::ID contextid = contextid_mda[0]; libmexclass::proxy::ID nocontextid(-1); // wrap around to intmax @@ -35,6 +32,17 @@ void LoggerProxy::emitLogRecord(libmexclass::proxy::method::Context& context) { size_t nattrs = attrnames_mda.getNumberOfElements(); matlab::data::CellArray attrvalues_mda = context.inputs[5]; + // log content + ProcessedAttributes contentattrs; + processAttribute("Body", content_mda, contentattrs); + common::AttributeValue log_content; + if (contentattrs.Attributes.empty()) { + log_content = ""; // invalid content + } else { + log_content = contentattrs.Attributes.front().second; + } + bool array_content = (contentattrs.Attributes.size() != 1); // non-scalar content will need to add size as attribute + nostd::unique_ptr rec = CppLogger->CreateLogRecord(); // context @@ -65,8 +73,11 @@ void LoggerProxy::emitLogRecord(libmexclass::proxy::method::Context& context) { {rec->SetAttribute(attr.first, attr.second);}; std::for_each(attrs.Attributes.cbegin(), attrs.Attributes.cend(), record_attribute); } + // Add size attribute if content is nonscalar + if (array_content) { + rec->SetAttribute(contentattrs.Attributes.back().first, contentattrs.Attributes.back().second); + } - CppLogger->EmitLogRecord(std::move(rec), static_cast(severity), nostd::string_view{content}); - + CppLogger->EmitLogRecord(std::move(rec), static_cast(severity), log_content); } } // namespace libmexclass::opentelemetry diff --git a/test/tlogs.m b/test/tlogs.m index 34ffc15..b3d0964 100644 --- a/test/tlogs.m +++ b/test/tlogs.m @@ -14,6 +14,7 @@ ExtractPid Sigint Sigterm + ForceFlushTimeout end methods (TestClassSetup) @@ -22,6 +23,7 @@ function setupOnce(testCase) utilsfolder = fullfile(fileparts(mfilename('fullpath')), "utils"); testCase.applyFixture(matlab.unittest.fixtures.PathFixture(utilsfolder)); commonSetupOnce(testCase); + testCase.ForceFlushTimeout = seconds(2); end end @@ -55,6 +57,7 @@ function testBasic(testCase) verifyEqual(testCase, lg.Schema, ""); % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); verifyNumElements(testCase, results, 1); if ~isempty(results) @@ -114,6 +117,7 @@ function testSeverity(testCase) end % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); verifyNumElements(testCase, results, nseverity); for i = 1:nseverity @@ -124,6 +128,54 @@ function testSeverity(testCase) end end + function testContent(testCase) + % testContent: Content of different data types + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, "foo"); + + % scalar + scalarcontent = {"abcde" 'fghi' 1 int32(5) true}; + scalarcontenttype = ["string" "string" "double" "int" "bool"] + "Value"; + nscalar = length(scalarcontent); + % array + arraycontent = {["abcde" "fghij"] {'klmn'; 'opqr'} magic(3) int64(magic(4)) [true false true]}; + arraycontenttype = ["string" "string" "double" "int" "bool"] + "Value"; + narray = length(arraycontent); + + content = [scalarcontent arraycontent]; + ncontent = nscalar + narray; + for i = 1:ncontent + emitLogRecord(lg, "Debug", content{i}); + end + + % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); + results = readJsonResults(testCase); + verifyNumElements(testCase, results, ncontent); + % check scalar results + for i = 1:nscalar + resultsi = results{i}; + + % compare in strings which works for all types + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.body.(scalarcontenttype(i))), ... + string(scalarcontent{i})); + end + % check array results + for i = 1:narray + resultsi = results{nscalar+i}; + + % compare in strings which works for all types + verifyEqual(testCase, string({resultsi.resourceLogs.scopeLogs.logRecords.body.arrayValue.values.(arraycontenttype(i))}), ... + string(reshape(arraycontent{i},1,[]))); + % compare array size + verifyNumElements(testCase, resultsi.resourceLogs.scopeLogs.logRecords.attributes, 1); + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.attributes.key), ... + "Body.size"); + verifyEqual(testCase, [resultsi.resourceLogs.scopeLogs.logRecords.attributes.value.arrayValue.values.doubleValue], ... + size(arraycontent{i})); + end + end + function testImplicitContext(testCase) % testImplicitContext: Test current trace and span IDs are recorded @@ -142,6 +194,7 @@ function testImplicitContext(testCase) endSpan(sp); % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); % check for two records: first a log and second a span @@ -180,6 +233,7 @@ function testExplicitContext(testCase) endSpan(sp); % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); log = results{1}; @@ -200,6 +254,7 @@ function testTimestamp(testCase) emitLogRecord(lg, "info", "bar", "Timestamp", timestamp); % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); verifyEqual(testCase, datetime(double(string(... results{1}.resourceLogs.scopeLogs.logRecords.timeUnixNano))/1e9, ... @@ -219,6 +274,7 @@ function testAttributes(testCase) emitLogRecord(lg, "warn", "bar", Attributes=attributes); % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); attrkeys = string({results{1}.resourceLogs.scopeLogs.logRecords.attributes.key}); @@ -331,6 +387,7 @@ function testSeverityFunctions(testCase) end % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); verifyNumElements(testCase, results, nfuncs); for i = 1:nfuncs @@ -362,6 +419,8 @@ function testGetSetLoggerProvider(testCase) emitLogRecord(lg, logseverity, logmessage); % perform test comparisons + opentelemetry.sdk.common.Cleanup.forceFlush(... + opentelemetry.logs.Provider.getLoggerProvider, testCase.ForceFlushTimeout); results = readJsonResults(testCase); % check log record, and check its resource to identify the diff --git a/test/tlogs_sdk.m b/test/tlogs_sdk.m index accc6c4..f7f6f39 100644 --- a/test/tlogs_sdk.m +++ b/test/tlogs_sdk.m @@ -14,6 +14,7 @@ ExtractPid Sigint Sigterm + ForceFlushTimeout end methods (TestClassSetup) @@ -22,6 +23,7 @@ function setupOnce(testCase) utilsfolder = fullfile(fileparts(mfilename('fullpath')), "utils"); testCase.applyFixture(matlab.unittest.fixtures.PathFixture(utilsfolder)); commonSetupOnce(testCase); + testCase.ForceFlushTimeout = seconds(2); end end @@ -55,6 +57,7 @@ function testAddLogRecordProcessor(testCase) % verify if the json results has two exported instances after % emitting a single log record + forceFlush(p, testCase.ForceFlushTimeout); results = readJsonResults(testCase); result_count = numel(results); verifyEqual(testCase,result_count, 2); @@ -84,10 +87,9 @@ function testBatchLogRecordProcessor(testCase) p = opentelemetry.sdk.logs.LoggerProvider(b); lg = p.getLogger(loggername); lg.emitLogRecord(logseverity, logcontent); - % flush the record - forceFlush(p, seconds(2)); % verify log content and severity + forceFlush(p, testCase.ForceFlushTimeout); results = readJsonResults(testCase); results = results{1}; verifyEqual(testCase, string(results.resourceLogs.scopeLogs.scope.name), loggername); @@ -106,6 +108,7 @@ function testCustomResource(testCase) emitLogRecord(lg, "debug", "qux"); % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); results = results{1}; @@ -128,6 +131,7 @@ function testShutdown(testCase) emitLogRecord(lg, "info", logcontent); % shutdown the logger provider + forceFlush(lp, testCase.ForceFlushTimeout); verifyTrue(testCase, shutdown(lp)); % emit another log record @@ -149,6 +153,7 @@ function testCleanupSdk(testCase) emitLogRecord(lg, "warn", logcontent); % shutdown the SDK logger provider through the Cleanup class + forceFlush(lp, testCase.ForceFlushTimeout); verifyTrue(testCase, opentelemetry.sdk.common.Cleanup.shutdown(lp)); % emit another log record @@ -173,6 +178,7 @@ function testCleanupApi(testCase) emitLogRecord(lg, "error", logcontent); % shutdown the API logger provider through the Cleanup class + opentelemetry.sdk.common.Cleanup.forceFlush(lp_api, testCase.ForceFlushTimeout); verifyTrue(testCase, opentelemetry.sdk.common.Cleanup.shutdown(lp_api)); % emit another log record From de8cc8747c5a916390f0bbead97f086125319740 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Wed, 10 Apr 2024 13:54:29 -0400 Subject: [PATCH 04/18] Rename log content to log body --- api/logs/+opentelemetry/+logs/Logger.m | 88 ++++++++++++-------------- api/logs/src/LoggerProxy.cpp | 26 ++++---- test/tlogs.m | 42 ++++++------ test/tlogs_sdk.m | 30 ++++----- 4 files changed, 90 insertions(+), 96 deletions(-) diff --git a/api/logs/+opentelemetry/+logs/Logger.m b/api/logs/+opentelemetry/+logs/Logger.m index 823824d..6305881 100644 --- a/api/logs/+opentelemetry/+logs/Logger.m +++ b/api/logs/+opentelemetry/+logs/Logger.m @@ -25,15 +25,15 @@ end methods - function emitLogRecord(obj, severity, content, trailingnames, trailingvalues) + function emitLogRecord(obj, severity, body, trailingnames, trailingvalues) % EMITLOGRECORD Create and emit a log record - % EMITLOGRECORD(LG, SEVERITY, CONTENT) emits a log record - % with the specified severity and content. Severity is one - % of "trace", "debug", "info", "warn", "error", and "fatal". It - % can also be a scalar integer between 1 and 24. Content can be an - % array of type double, int32, uint32, int64, logical, or string. + % EMITLOGRECORD(LG, SEVERITY, BODY) emits a log record + % with the specified severity and body. Severity can be one + % of "trace", "debug", "info", "warn", "error", and "fatal", + % or it can also be a scalar integer between 1 and 24. Body + % can be any string, numeric, or logical scalar or array. % - % EMITLOGRECORD(LG, SEVERITY, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % EMITLOGRECORD(LG, SEVERITY, BODY, NAME, PARAM1, VALUE1, PARAM2, VALUE2, % ...) specifies optional parameter name/value pairs. % Parameters are: % "Context" - Span contained in a context object. @@ -48,7 +48,7 @@ function emitLogRecord(obj, severity, content, trailingnames, trailingvalues) arguments obj severity - content + body end arguments (Repeating) trailingnames @@ -77,8 +77,8 @@ function emitLogRecord(obj, severity, content, trailingnames, trailingvalues) severity = 0; end - % content - content = convertCharsToStrings(content); % force char rows into strings + % body + body = convertCharsToStrings(body); % force char rows into strings % validate the trailing names and values optionnames = ["Context", "Timestamp", "Attributes"]; @@ -120,17 +120,16 @@ function emitLogRecord(obj, severity, content, trailingnames, trailingvalues) end end end - obj.Proxy.emitLogRecord(severity, content, contextid, timestamp, ... + obj.Proxy.emitLogRecord(severity, body, contextid, timestamp, ... attributekeys, attributevalues); end - function trace(obj, content, varargin) + function trace(obj, body, varargin) % TRACE Create and emit a log record with "trace" severity - % TRACE(LG, CONTENT) emits a log record with "trace" severity. - % Content can be an array of type double, int32, uint32, - % int64, logical, or string. + % TRACE(LG, BODY) emits a log record with "trace" severity. + % Body can be any string, numeric, or logical scalar or array. % - % TRACE(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % TRACE(LG, BODY, NAME, PARAM1, VALUE1, PARAM2, VALUE2, % ...) specifies optional parameter name/value pairs. % Parameters are: % "Context" - Span contained in a context object. @@ -143,16 +142,15 @@ function trace(obj, content, varargin) % OPENTELEMETRY.LOGS.INFO, OPENTELEMETRY.LOGS.WARN, % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL, % OPENTELEMETRY.LOGS.EMITLOGRECORD - emitLogRecord(obj, "trace", content, varargin{:}); + emitLogRecord(obj, "trace", body, varargin{:}); end - function debug(obj, content, varargin) + function debug(obj, body, varargin) % DEBUG Create and emit a log record with "debug" severity - % DEBUG(LG, CONTENT) emits a log record with "debug" severity. - % Content can be an array of type double, int32, uint32, - % int64, logical, or string. + % DEBUG(LG, BODY) emits a log record with "debug" severity. + % Body can be any string, numeric, or logical scalar or array. % - % DEBUG(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % DEBUG(LG, BODY, NAME, PARAM1, VALUE1, PARAM2, VALUE2, % ...) specifies optional parameter name/value pairs. % Parameters are: % "Context" - Span contained in a context object. @@ -165,16 +163,15 @@ function debug(obj, content, varargin) % OPENTELEMETRY.LOGS.INFO, OPENTELEMETRY.LOGS.WARN, % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL, % OPENTELEMETRY.LOGS.EMITLOGRECORD - emitLogRecord(obj, "debug", content, varargin{:}); + emitLogRecord(obj, "debug", body, varargin{:}); end - function info(obj, content, varargin) + function info(obj, body, varargin) % INFO Create and emit a log record with "info" severity - % INFO(LG, CONTENT) emits a log record with "info" severity. - % Content can be an array of type double, int32, uint32, - % int64, logical, or string. + % INFO(LG, BODY) emits a log record with "info" severity. + % Body can be any string, numeric, or logical scalar or array. % - % INFO(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % INFO(LG, BODY, NAME, PARAM1, VALUE1, PARAM2, VALUE2, % ...) specifies optional parameter name/value pairs. % Parameters are: % "Context" - Span contained in a context object. @@ -187,16 +184,15 @@ function info(obj, content, varargin) % OPENTELEMETRY.LOGS.DEBUG, OPENTELEMETRY.LOGS.WARN, % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL, % OPENTELEMETRY.LOGS.EMITLOGRECORD - emitLogRecord(obj, "info", content, varargin{:}); + emitLogRecord(obj, "info", body, varargin{:}); end - function warn(obj, content, varargin) + function warn(obj, body, varargin) % WARN Create and emit a log record with "warn" severity - % WARN(LG, CONTENT) emits a log record with "warn" severity. - % Content can be an array of type double, int32, uint32, - % int64, logical, or string. + % WARN(LG, BODY) emits a log record with "warn" severity. + % Body can be any string, numeric, or logical scalar or array. % - % WARN(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % WARN(LG, BODY, NAME, PARAM1, VALUE1, PARAM2, VALUE2, % ...) specifies optional parameter name/value pairs. % Parameters are: % "Context" - Span contained in a context object. @@ -209,16 +205,15 @@ function warn(obj, content, varargin) % OPENTELEMETRY.LOGS.DEBUG, OPENTELEMETRY.LOGS.INFO, % OPENTELEMETRY.LOGS.ERROR, OPENTELEMETRY.LOGS.FATAL, % OPENTELEMETRY.LOGS.EMITLOGRECORD - emitLogRecord(obj, "warn", content, varargin{:}); + emitLogRecord(obj, "warn", body, varargin{:}); end - function error(obj, content, varargin) + function error(obj, body, varargin) % ERROR Create and emit a log record with "error" severity - % ERROR(LG, CONTENT) emits a log record with "error" severity. - % Content can be an array of type double, int32, uint32, - % int64, logical, or string. + % ERROR(LG, BODY) emits a log record with "error" severity. + % Body can be any string, numeric, or logical scalar or array. % - % ERROR(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % ERROR(LG, BODY, NAME, PARAM1, VALUE1, PARAM2, VALUE2, % ...) specifies optional parameter name/value pairs. % Parameters are: % "Context" - Span contained in a context object. @@ -231,16 +226,15 @@ function error(obj, content, varargin) % OPENTELEMETRY.LOGS.DEBUG, OPENTELEMETRY.LOGS.INFO, % OPENTELEMETRY.LOGS.WARN, OPENTELEMETRY.LOGS.FATAL, % OPENTELEMETRY.LOGS.EMITLOGRECORD - emitLogRecord(obj, "error", content, varargin{:}); + emitLogRecord(obj, "error", body, varargin{:}); end - function fatal(obj, content, varargin) + function fatal(obj, body, varargin) % FATAL Create and emit a log record with "fatal" severity - % FATAL(LG, CONTENT) emits a log record with "fatal" severity. - % Content can be an array of type double, int32, uint32, - % int64, logical, or string. + % FATAL(LG, BODY) emits a log record with "fatal" severity. + % Body can be any string, numeric, or logical scalar or array. % - % FATAL(LG, CONTENT, NAME, PARAM1, VALUE1, PARAM2, VALUE2, + % FATAL(LG, BODY, NAME, PARAM1, VALUE1, PARAM2, VALUE2, % ...) specifies optional parameter name/value pairs. % Parameters are: % "Context" - Span contained in a context object. @@ -253,7 +247,7 @@ function fatal(obj, content, varargin) % OPENTELEMETRY.LOGS.DEBUG, OPENTELEMETRY.LOGS.INFO, % OPENTELEMETRY.LOGS.WARN, OPENTELEMETRY.LOGS.ERROR, % OPENTELEMETRY.LOGS.EMITLOGRECORD - emitLogRecord(obj, "fatal", content, varargin{:}); + emitLogRecord(obj, "fatal", body, varargin{:}); end end diff --git a/api/logs/src/LoggerProxy.cpp b/api/logs/src/LoggerProxy.cpp index 95c4889..f6fc50e 100644 --- a/api/logs/src/LoggerProxy.cpp +++ b/api/logs/src/LoggerProxy.cpp @@ -22,7 +22,7 @@ namespace libmexclass::opentelemetry { void LoggerProxy::emitLogRecord(libmexclass::proxy::method::Context& context) { matlab::data::TypedArray severity_mda = context.inputs[0]; int severity = static_cast(severity_mda[0]); - matlab::data::Array content_mda = context.inputs[1]; + matlab::data::Array body_mda = context.inputs[1]; matlab::data::TypedArray contextid_mda = context.inputs[2]; libmexclass::proxy::ID contextid = contextid_mda[0]; libmexclass::proxy::ID nocontextid(-1); // wrap around to intmax @@ -32,16 +32,16 @@ void LoggerProxy::emitLogRecord(libmexclass::proxy::method::Context& context) { size_t nattrs = attrnames_mda.getNumberOfElements(); matlab::data::CellArray attrvalues_mda = context.inputs[5]; - // log content - ProcessedAttributes contentattrs; - processAttribute("Body", content_mda, contentattrs); - common::AttributeValue log_content; - if (contentattrs.Attributes.empty()) { - log_content = ""; // invalid content + // log body + ProcessedAttributes bodyattrs; + processAttribute("Body", body_mda, bodyattrs); + common::AttributeValue log_body; + if (bodyattrs.Attributes.empty()) { + log_body = ""; // invalid body } else { - log_content = contentattrs.Attributes.front().second; + log_body = bodyattrs.Attributes.front().second; } - bool array_content = (contentattrs.Attributes.size() != 1); // non-scalar content will need to add size as attribute + bool array_body = (bodyattrs.Attributes.size() != 1); // non-scalar body will need to add size as attribute nostd::unique_ptr rec = CppLogger->CreateLogRecord(); @@ -73,11 +73,11 @@ void LoggerProxy::emitLogRecord(libmexclass::proxy::method::Context& context) { {rec->SetAttribute(attr.first, attr.second);}; std::for_each(attrs.Attributes.cbegin(), attrs.Attributes.cend(), record_attribute); } - // Add size attribute if content is nonscalar - if (array_content) { - rec->SetAttribute(contentattrs.Attributes.back().first, contentattrs.Attributes.back().second); + // Add size attribute if body is nonscalar + if (array_body) { + rec->SetAttribute(bodyattrs.Attributes.back().first, bodyattrs.Attributes.back().second); } - CppLogger->EmitLogRecord(std::move(rec), static_cast(severity), log_content); + CppLogger->EmitLogRecord(std::move(rec), static_cast(severity), log_body); } } // namespace libmexclass::opentelemetry diff --git a/test/tlogs.m b/test/tlogs.m index b3d0964..938f36f 100644 --- a/test/tlogs.m +++ b/test/tlogs.m @@ -63,7 +63,7 @@ function testBasic(testCase) if ~isempty(results) results = results{1}; - % check logger name, log content and severity, trace and span IDs + % check logger name, log body and severity, trace and span IDs verifyEqual(testCase, string(results.resourceLogs.scopeLogs.scope.name), loggername); verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.severityText), upper(logseverity)); verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.body.stringValue), logmessage); @@ -128,51 +128,51 @@ function testSeverity(testCase) end end - function testContent(testCase) - % testContent: Content of different data types + function testBody(testCase) + % testBody: Body of different data types lp = opentelemetry.sdk.logs.LoggerProvider(); lg = getLogger(lp, "foo"); % scalar - scalarcontent = {"abcde" 'fghi' 1 int32(5) true}; - scalarcontenttype = ["string" "string" "double" "int" "bool"] + "Value"; - nscalar = length(scalarcontent); + scalarbody = {"abcde" 'fghi' 1 int32(5) true}; + scalarbodytype = ["string" "string" "double" "int" "bool"] + "Value"; + nscalar = length(scalarbody); % array - arraycontent = {["abcde" "fghij"] {'klmn'; 'opqr'} magic(3) int64(magic(4)) [true false true]}; - arraycontenttype = ["string" "string" "double" "int" "bool"] + "Value"; - narray = length(arraycontent); - - content = [scalarcontent arraycontent]; - ncontent = nscalar + narray; - for i = 1:ncontent - emitLogRecord(lg, "Debug", content{i}); + arraybody = {["abcde" "fghij"] {'klmn'; 'opqr'} magic(3) int64(magic(4)) [true false true]}; + arraybodytype = ["string" "string" "double" "int" "bool"] + "Value"; + narray = length(arraybody); + + body = [scalarbody arraybody]; + nbody = nscalar + narray; + for i = 1:nbody + emitLogRecord(lg, "Debug", body{i}); end % perform test comparisons forceFlush(lp, testCase.ForceFlushTimeout); results = readJsonResults(testCase); - verifyNumElements(testCase, results, ncontent); + verifyNumElements(testCase, results, nbody); % check scalar results for i = 1:nscalar resultsi = results{i}; % compare in strings which works for all types - verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.body.(scalarcontenttype(i))), ... - string(scalarcontent{i})); + verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.body.(scalarbodytype(i))), ... + string(scalarbody{i})); end % check array results for i = 1:narray resultsi = results{nscalar+i}; % compare in strings which works for all types - verifyEqual(testCase, string({resultsi.resourceLogs.scopeLogs.logRecords.body.arrayValue.values.(arraycontenttype(i))}), ... - string(reshape(arraycontent{i},1,[]))); + verifyEqual(testCase, string({resultsi.resourceLogs.scopeLogs.logRecords.body.arrayValue.values.(arraybodytype(i))}), ... + string(reshape(arraybody{i},1,[]))); % compare array size verifyNumElements(testCase, resultsi.resourceLogs.scopeLogs.logRecords.attributes, 1); verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.attributes.key), ... "Body.size"); verifyEqual(testCase, [resultsi.resourceLogs.scopeLogs.logRecords.attributes.value.arrayValue.values.doubleValue], ... - size(arraycontent{i})); + size(arraybody{i})); end end @@ -393,7 +393,7 @@ function testSeverityFunctions(testCase) for i = 1:nfuncs resultsi = results{i}; - % check logger name, log content and severity, trace and span IDs + % check logger name, log body and severity, trace and span IDs verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.scope.name), loggername); verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.severityText), upper(logseverity(i))); verifyEqual(testCase, string(resultsi.resourceLogs.scopeLogs.logRecords.body.stringValue), logmessage); diff --git a/test/tlogs_sdk.m b/test/tlogs_sdk.m index f7f6f39..7e14d75 100644 --- a/test/tlogs_sdk.m +++ b/test/tlogs_sdk.m @@ -43,13 +43,13 @@ function teardown(testCase) function testAddLogRecordProcessor(testCase) % testAddLogRecordProcessor: addLogRecordProcessor method loggername = "foo"; - logcontent = "bar"; + logbody = "bar"; processor1 = opentelemetry.sdk.logs.SimpleLogRecordProcessor; processor2 = opentelemetry.sdk.logs.SimpleLogRecordProcessor; p = opentelemetry.sdk.logs.LoggerProvider(processor1); p.addLogRecordProcessor(processor2); lg = p.getLogger(loggername); - lg.emitLogRecord("debug", logcontent); + lg.emitLogRecord("debug", logbody); % verify if the provider has two log processors attached processor_count = numel(p.LogRecordProcessor); @@ -68,7 +68,7 @@ function testBatchLogRecordProcessor(testCase) % BatchRecordProcessor loggername = "foo"; logseverity = "debug"; - logcontent = "bar"; + logbody = "bar"; queuesize = 500; delay = seconds(2); batchsize = 50; @@ -86,15 +86,15 @@ function testBatchLogRecordProcessor(testCase) p = opentelemetry.sdk.logs.LoggerProvider(b); lg = p.getLogger(loggername); - lg.emitLogRecord(logseverity, logcontent); + lg.emitLogRecord(logseverity, logbody); - % verify log content and severity + % verify log body and severity forceFlush(p, testCase.ForceFlushTimeout); results = readJsonResults(testCase); results = results{1}; verifyEqual(testCase, string(results.resourceLogs.scopeLogs.scope.name), loggername); verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.severityText), upper(logseverity)); - verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.body.stringValue), logcontent); + verifyEqual(testCase, string(results.resourceLogs.scopeLogs.logRecords.body.stringValue), logbody); end function testCustomResource(testCase) @@ -127,8 +127,8 @@ function testShutdown(testCase) lg = getLogger(lp, "foo"); % emit a log record - logcontent = "bar"; - emitLogRecord(lg, "info", logcontent); + logbody = "bar"; + emitLogRecord(lg, "info", logbody); % shutdown the logger provider forceFlush(lp, testCase.ForceFlushTimeout); @@ -140,7 +140,7 @@ function testShutdown(testCase) % verify only the first log record was emitted results = readJsonResults(testCase); verifyNumElements(testCase, results, 1); - verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logcontent); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logbody); end function testCleanupSdk(testCase) @@ -149,8 +149,8 @@ function testCleanupSdk(testCase) lg = getLogger(lp, "foo"); % emit a log record - logcontent = "bar"; - emitLogRecord(lg, "warn", logcontent); + logbody = "bar"; + emitLogRecord(lg, "warn", logbody); % shutdown the SDK logger provider through the Cleanup class forceFlush(lp, testCase.ForceFlushTimeout); @@ -162,7 +162,7 @@ function testCleanupSdk(testCase) % verify only the first log record was recorded results = readJsonResults(testCase); verifyNumElements(testCase, results, 1); - verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logcontent); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logbody); end function testCleanupApi(testCase) @@ -174,8 +174,8 @@ function testCleanupApi(testCase) lg = getLogger(lp_api, "foo"); % emit a log record - logcontent = "bar"; - emitLogRecord(lg, "error", logcontent); + logbody = "bar"; + emitLogRecord(lg, "error", logbody); % shutdown the API logger provider through the Cleanup class opentelemetry.sdk.common.Cleanup.forceFlush(lp_api, testCase.ForceFlushTimeout); @@ -187,7 +187,7 @@ function testCleanupApi(testCase) % verify only the first log record was recorded results = readJsonResults(testCase); verifyNumElements(testCase, results, 1); - verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logcontent); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.body.stringValue), logbody); end end end \ No newline at end of file From 6e9e74feda7e2e2f845f55103d5fddcfc0b665f5 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Wed, 10 Apr 2024 19:23:46 -0400 Subject: [PATCH 05/18] fix a typo --- api/logs/src/LoggerProxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/logs/src/LoggerProxy.cpp b/api/logs/src/LoggerProxy.cpp index f6fc50e..a0f5612 100644 --- a/api/logs/src/LoggerProxy.cpp +++ b/api/logs/src/LoggerProxy.cpp @@ -8,7 +8,7 @@ #include "opentelemetry/logs/severity.h" #include "opentelemetry/logs/log_record.h" #include "opentelemetry/trace/context.h" -#include "opentelemetry/context/Context.h" +#include "opentelemetry/context/context.h" #include "MatlabDataArray.hpp" From 17c7324c35221cba4410fc7eda85b3fbde70c601 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Thu, 11 Apr 2024 08:14:00 -0400 Subject: [PATCH 06/18] fix Linux missing symbol issue --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0da7ae..2cfe86f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,6 +354,7 @@ target_link_libraries(${OPENTELEMETRY_PROXY_LIBRARY_NAME} PRIVATE ${OPENTELEMETR if(UNIX AND NOT APPLE AND NOT CYGWIN) set(OPENTELEMETRY_PROXY_LINK_OPTIONS -Wl,--whole-archive "${OTEL_CPP_PREFIX}/lib/libopentelemetry_trace.a" + "${OTEL_CPP_PREFIX}/lib/libopentelemetry_logs.a" "${OTEL_CPP_PREFIX}/lib/libopentelemetry_common.a" "${OTEL_CPP_PREFIX}/lib/libopentelemetry_otlp_recordable.a" ${ABSL_LIBRARIES} From f50eb165efb5f7b4dcef9107bff7472221eb5605 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Thu, 11 Apr 2024 15:13:41 -0400 Subject: [PATCH 07/18] Upload uncompressed .mltbx file with both Http and gRPC exporters #107, #101 --- .github/workflows/publish_mltbx.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/publish_mltbx.yml b/.github/workflows/publish_mltbx.yml index 4d5d528..86b52c7 100644 --- a/.github/workflows/publish_mltbx.yml +++ b/.github/workflows/publish_mltbx.yml @@ -24,11 +24,11 @@ jobs: - name: Build OpenTelemetry-Matlab working-directory: opentelemetry-matlab run: | - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_GRPC=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Compress into single artifact working-directory: ${{ github.workspace }} - run: tar -czf otel-matlab-ubuntu.tar.gz otel_matlab_install + run: tar -czf otel-matlab-ubuntu.tar.gz ${{ env.OPENTELEMETRY_MATLAB_INSTALL}} - name: Upload artifacts uses: actions/upload-artifact@v2 with: @@ -54,11 +54,11 @@ jobs: shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 - cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_GRPC=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Compress into single artifact working-directory: ${{ github.workspace }} - run: tar -czf otel-matlab-windows.tar.gz otel_matlab_install + run: tar -czf otel-matlab-windows.tar.gz ${{ env.OPENTELEMETRY_MATLAB_INSTALL }} - name: Upload artifacts uses: actions/upload-artifact@v2 with: @@ -82,11 +82,11 @@ jobs: - name: Build OpenTelemetry-Matlab working-directory: opentelemetry-matlab run: | - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_GRPC=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Compress into single artifact working-directory: ${{ github.workspace }} - run: tar -czf otel-matlab-macos.tar.gz otel_matlab_install + run: tar -czf otel-matlab-macos.tar.gz ${{ env.OPENTELEMETRY_MATLAB_INSTALL }} - name: Upload artifacts uses: actions/upload-artifact@v2 with: @@ -133,14 +133,12 @@ jobs: uses: matlab-actions/run-command@v1 with: command: packageMatlabInterface - - name: Compress Asset - run: zip otel-matlab-${{ github.event.release.tag_name }}.mltbx.zip otel-matlab.mltbx - name: Upload Release Asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} - asset_path: ./otel-matlab-${{ github.event.release.tag_name }}.mltbx.zip - asset_name: otel-matlab-${{ github.event.release.tag_name }}.mltbx.zip - asset_content_type: application/zip + asset_path: ./otel-matlab.mltbx + asset_name: otel-matlab.mltbx + asset_content_type: application/octet-stream From cca9f80824092fcb5dcf57df395140f0b1674566 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Fri, 12 Apr 2024 14:22:41 -0400 Subject: [PATCH 08/18] fix log body with unsupported type --- api/logs/src/LoggerProxy.cpp | 4 +++- test/tlogs.m | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/api/logs/src/LoggerProxy.cpp b/api/logs/src/LoggerProxy.cpp index a0f5612..bbdb055 100644 --- a/api/logs/src/LoggerProxy.cpp +++ b/api/logs/src/LoggerProxy.cpp @@ -41,7 +41,9 @@ void LoggerProxy::emitLogRecord(libmexclass::proxy::method::Context& context) { } else { log_body = bodyattrs.Attributes.front().second; } - bool array_body = (bodyattrs.Attributes.size() != 1); // non-scalar body will need to add size as attribute + // if body is nonscalar, bodyattrs.Attribute will contain an additional element + // which is the array size + bool array_body = (bodyattrs.Attributes.size() > 1); nostd::unique_ptr rec = CppLogger->CreateLogRecord(); diff --git a/test/tlogs.m b/test/tlogs.m index 938f36f..fff5c9c 100644 --- a/test/tlogs.m +++ b/test/tlogs.m @@ -176,6 +176,26 @@ function testBody(testCase) end end + function testBodyUnsupportedType(testCase) + % testBody: Body of an unsupported type + lp = opentelemetry.sdk.logs.LoggerProvider(); + lg = getLogger(lp, "foo"); + + nbody = 2; + emitLogRecord(lg, "Warn", datetime("now")); % datetime scalar + emitLogRecord(lg, "Warn", hours(1:4)); % duration array + + % perform test comparisons + forceFlush(lp, testCase.ForceFlushTimeout); + results = readJsonResults(testCase); + verifyNumElements(testCase, results, nbody); + % check results + for i = 1:nbody + % body should be an empty string + verifyEmpty(testCase, results{i}.resourceLogs.scopeLogs.logRecords.body.stringValue); + end + end + function testImplicitContext(testCase) % testImplicitContext: Test current trace and span IDs are recorded From 9556c2067d0607e943eff1edee4bd418608845e1 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Fri, 19 Apr 2024 10:21:03 -0400 Subject: [PATCH 09/18] fix type support in attributes #103 --- .../+common/mustBeScalarString.m | 4 +- .../+common/processAttributes.m | 102 +++++++++++ api/logs/+opentelemetry/+logs/Logger.m | 12 +- .../+metrics/SynchronousInstrument.m | 14 +- api/trace/+opentelemetry/+trace/Span.m | 94 ++-------- api/trace/+opentelemetry/+trace/Tracer.m | 20 +-- test/tlogs.m | 156 +++++++++-------- test/ttrace.m | 162 +++++++++--------- 8 files changed, 289 insertions(+), 275 deletions(-) create mode 100644 api/common/+opentelemetry/+common/processAttributes.m diff --git a/api/common/+opentelemetry/+common/mustBeScalarString.m b/api/common/+opentelemetry/+common/mustBeScalarString.m index 345990f..be1b8f9 100644 --- a/api/common/+opentelemetry/+common/mustBeScalarString.m +++ b/api/common/+opentelemetry/+common/mustBeScalarString.m @@ -1,11 +1,11 @@ function str = mustBeScalarString(str) % Convert into a scalar string -% STR = OPENTELEMETRY.UTILS.MUSTBESCALARSTRING(X) converts X into a +% STR = OPENTELEMETRY.COMMON.MUSTBESCALARSTRING(X) converts X into a % scalar string. If X is an array, only the first element will be kept. % If X is empty, an empty string is returned. If X cannot be converted % into string, an error will be thrown. -% Copyright 2023 The MathWorks, Inc. +% Copyright 2023-2024 The MathWorks, Inc. try str = string(str); catch diff --git a/api/common/+opentelemetry/+common/processAttributes.m b/api/common/+opentelemetry/+common/processAttributes.m new file mode 100644 index 0000000..7cd89dc --- /dev/null +++ b/api/common/+opentelemetry/+common/processAttributes.m @@ -0,0 +1,102 @@ +function [attributekeys, attributevalues] = processAttributes(attrsin, onlydictionary) +% Perform error checking and type conversion for attributes +% [PROCESSEDATTRNAMES, PROCESSEDATTRVALUES] = OPENTELEMETRY.COMMON.PROCESSATTRIBUTES(ATTRS) +% performs error checking and type conversion on attributes ATTRS. ATTRS +% is either a scalar dictionary, a scalar cell containing a dictionary, +% or a cell array containing name-value pairs. Returns valid attribute names +% and values converted to types supported by the underlying OpenTelemetry-cpp +% library. An attribute is invalid if its name is not a scalar string or +% a char row, or if its value is not numeric, logical, string, or +% cellstr. +% +% [...] = OPENTELEMETRY.COMMON.PROCESSATTRIBUTES(ATTRS, true) restrict +% ATTRS to be a dictionary only. Name-value pairs are not allowed. + +% Copyright 2024 The MathWorks, Inc. + +if nargin < 2 + onlydictionary = false; +end + +if isscalar(attrsin) && (isa(attrsin, "dictionary") || (iscell(attrsin) && ... + isa(attrsin{1}, "dictionary"))) + % dictionary case + if iscell(attrsin) + attrsin = attrsin{1}; + end + attributekeys = keys(attrsin); + attributevalues = values(attrsin,"cell"); + % collapse one level of cells, as this may be due to + % a behavior of dictionary.values + if all(cellfun(@iscell, attributevalues)) + attributevalues = [attributevalues{:}]; + end + % perform error checking and type conversion + validattrs = false(size(attributekeys)); + for j = 1:length(attributekeys) + [validattrs(j), attributekeys(j), attributevalues{j}] = ... + processAttribute(attributekeys(j), attributevalues{j}); + end + % remove the invalid attributes + attributekeys = attributekeys(validattrs); + attributevalues = attributevalues(validattrs); + +elseif ~onlydictionary && iscell(attrsin) && isrow(attrsin) + % NV pairs + nin = length(attrsin); + if rem(nin,2) ~= 0 + % Mismatched name-value pairs. Ignore all attributes. + attributekeys = strings(1,0); + attributevalues = cell(1,0); + return + end + nattrs = nin / 2; + attributekeys = strings(1,nattrs); + attributevalues = cell(1,nattrs); + validattrs = true(1,nattrs); + currindex = 1; + for i = 1:nattrs + attrname = attrsin{currindex}; + attrvalue = attrsin{currindex+1}; + [validattrs(i), attributekeys(i), attributevalues{i}] = processAttribute(... + attrname, attrvalue); + currindex = currindex + 2; + end + % remove the invalid attributes + attributekeys = attributekeys(validattrs); + attributevalues = attributevalues(validattrs); +else + % invalid attributes + attributekeys = strings(1,0); + attributevalues = cell(1,0); + return +end +end + +function [isvalid, attrname, attrval] = processAttribute(attrname, attrval) +% Local helper for an individual attribute + +if ~(isStringScalar(attrname) || (ischar(attrname) && isrow(attrname))) + isvalid = false; + return +else + attrname = string(attrname); +end +if isfloat(attrval) + attrval = double(attrval); +elseif isinteger(attrval) + if isa(attrval, "int8") || isa(attrval, "int16") + attrval = int32(attrval); + elseif isa(attrval, "uint8") || isa(attrval, "uint16") + attrval = uint32(attrval); + elseif isa(attrval, "uint64") + attrval = int64(attrval); + end +elseif (ischar(attrval) && isrow(attrval)) || iscellstr(attrval) + attrval = string(attrval); +elseif ~(isstring(attrval) || islogical(attrval)) + isvalid = false; + return +end +isvalid = true; +end \ No newline at end of file diff --git a/api/logs/+opentelemetry/+logs/Logger.m b/api/logs/+opentelemetry/+logs/Logger.m index 6305881..d8b4181 100644 --- a/api/logs/+opentelemetry/+logs/Logger.m +++ b/api/logs/+opentelemetry/+logs/Logger.m @@ -54,6 +54,7 @@ function emitLogRecord(obj, severity, body, trailingnames, trailingvalues) trailingnames trailingvalues end + import opentelemetry.common.processAttributes if isnumeric(severity) && isscalar(severity) % severity number if severity < 1 || severity > 24 || round(severity) ~= severity @@ -108,16 +109,7 @@ function emitLogRecord(obj, severity, body, trailingnames, trailingvalues) timestamp = posixtime(valuei); end elseif strcmp(namei, "Attributes") - valuei = trailingvalues{i}; - if isa(valuei, "dictionary") - attributekeys = keys(valuei); - attributevalues = values(valuei,"cell"); - % collapse one level of cells, as this may be due to - % a behavior of dictionary.values - if all(cellfun(@iscell, attributevalues)) - attributevalues = [attributevalues{:}]; - end - end + [attributekeys, attributevalues] = processAttributes(trailingvalues{i}, true); end end obj.Proxy.emitLogRecord(severity, body, contextid, timestamp, ... diff --git a/api/metrics/+opentelemetry/+metrics/SynchronousInstrument.m b/api/metrics/+opentelemetry/+metrics/SynchronousInstrument.m index 91f92e7..e5e6fad 100644 --- a/api/metrics/+opentelemetry/+metrics/SynchronousInstrument.m +++ b/api/metrics/+opentelemetry/+metrics/SynchronousInstrument.m @@ -22,21 +22,15 @@ end function processValue(obj, value, varargin) + import opentelemetry.common.processAttributes % input value must be a numerical real scalar if isnumeric(value) && isscalar(value) && isreal(value) if nargin == 2 obj.Proxy.processValue(value); - elseif isa(varargin{1}, "dictionary") - attrkeys = keys(varargin{1}); - attrvals = values(varargin{1},"cell"); - if all(cellfun(@iscell, attrvals)) - attrvals = [attrvals{:}]; - end - obj.Proxy.processValue(value, attrkeys, attrvals); else - attrkeys = [varargin{1:2:length(varargin)}]'; - attrvals = [varargin(2:2:length(varargin))]'; - obj.Proxy.processValue(value, attrkeys, attrvals); + % attributes + [attrkeys, attrvalues] = processAttributes(varargin); + obj.Proxy.processValue(value, attrkeys, attrvalues); end end end diff --git a/api/trace/+opentelemetry/+trace/Span.m b/api/trace/+opentelemetry/+trace/Span.m index 3ac71b7..2e0d9db 100644 --- a/api/trace/+opentelemetry/+trace/Span.m +++ b/api/trace/+opentelemetry/+trace/Span.m @@ -1,7 +1,7 @@ classdef Span < handle % A span that represents a unit of work within a trace. -% Copyright 2023 The MathWorks, Inc. +% Copyright 2023-2024 The MathWorks, Inc. properties Name (1,1) string % Name of span @@ -84,11 +84,10 @@ function setAttributes(obj, varargin) % name-value pairs. % % See also ADDEVENT - attrs = obj.processAttributes(varargin); - - attrslen = length(attrs); - for i = 1:2:attrslen - obj.Proxy.setAttribute(attrs{i}, attrs{i+1}); + [attrnames, attrvalues] = opentelemetry.common.processAttributes(varargin); + + for i = 1:length(attrnames) + obj.Proxy.setAttribute(attrnames(i), attrvalues{i}); end end @@ -115,7 +114,12 @@ function addEvent(obj, eventname, varargin) end eventname = opentelemetry.common.mustBeScalarString(eventname); - attrs = obj.processAttributes(varargin); + [attrnames, attrvalues] = opentelemetry.common.processAttributes(varargin); + attrs = cell(2,length(attrnames)); + for i = 1:length(attrnames) + attrs{1,i} = attrnames(i); + attrs(2,i) = attrvalues(i); + end obj.Proxy.addEvent(eventname, eventtime, attrs{:}); end @@ -180,80 +184,4 @@ function setStatus(obj, status, description) context = opentelemetry.context.Context(contextproxy); end end - - methods (Static, Access=private) - function attrs = processAttributes(attrsin) - import opentelemetry.trace.Span.processAttribute - - if isscalar(attrsin) && isa(attrsin{1}, "dictionary") - % dictionary case - attrtbl = entries(attrsin{1}); - nattr = height(attrtbl); - if ~iscell(attrtbl.(2)) % force attribute values to be cell array - attrtbl.(2) = mat2cell(attrtbl.(2),ones(1, nattr)); - end - validattrs = true(1,nattr); - attrs = cell(2,nattr); - for i = 1:nattr - attrname = attrtbl{i,1}; - attrvalue = attrtbl{i,2}{1}; - [validattrs(i), attrname, attrvalue] = processAttribute(attrname, attrvalue); - attrs{1,i} = attrname; - attrs{2,i} = attrvalue; - end - % remove the invalid attributes - attrs(:, ~validattrs) = []; - attrs = attrs(:); - else - % NV pairs - nin = length(attrsin); - if rem(nin,2) ~= 0 - % Mismatched name-value pairs. Ignore all attributes. - attrs = cell(1,0); - return - end - validattrs = true(1,nin); - attrs = cell(1,nin); - for i = 1:2:nin - attrname = attrsin{i}; - attrvalue = attrsin{i+1}; - [validattrs(i), attrname, attrvalue] = processAttribute(attrname, attrvalue); - validattrs(i+1) = validattrs(i); - attrs{i} = attrname; - attrs{i+1} = attrvalue; - end - % remove the invalid attributes - attrs(~validattrs) = []; - end - end - - function [isvalid, attrname, attrval] = processAttribute(attrname, attrval) - % check for errors, and perform type conversion for an - % individual attribute - if ~(isStringScalar(attrname) || (ischar(attrname) && isrow(attrname))) - isvalid = false; - return - else - attrname = string(attrname); - end - if isfloat(attrval) - attrval = double(attrval); - elseif isinteger(attrval) - if isa(attrval, "int8") || isa(attrval, "int16") - attrval = int32(attrval); - elseif isa(attrval, "uint8") || isa(attrval, "uint16") - attrval = uint32(attrval); - elseif isa(attrval, "uint64") - attrval = int64(attrval); - end - elseif ischar(attrval) && isrow(attrval) - attrval = string(attrval); - elseif ~(isstring(attrval) || islogical(attrval)) - isvalid = false; - return - end - isvalid = true; - end - end - end diff --git a/api/trace/+opentelemetry/+trace/Tracer.m b/api/trace/+opentelemetry/+trace/Tracer.m index 772958f..f9af846 100644 --- a/api/trace/+opentelemetry/+trace/Tracer.m +++ b/api/trace/+opentelemetry/+trace/Tracer.m @@ -53,6 +53,7 @@ trailingnames trailingvalues end + import opentelemetry.common.processAttributes % validate the trailing names and values optionnames = ["Context", "SpanKind", "StartTime", "Attributes", "Links"]; % define default values @@ -88,16 +89,7 @@ starttime = posixtime(valuei); end elseif strcmp(namei, "Attributes") - valuei = trailingvalues{i}; - if isa(valuei, "dictionary") - attributekeys = keys(valuei); - attributevalues = values(valuei,"cell"); - % collapse one level of cells, as this may be due to - % a behavior of dictionary.values - if all(cellfun(@iscell, attributevalues)) - attributevalues = [attributevalues{:}]; - end - end + [attributekeys, attributevalues] = processAttributes(trailingvalues{i}, true); elseif strcmp(namei, "Links") valuei = trailingvalues{i}; if isa(valuei, "opentelemetry.trace.Link") @@ -106,13 +98,7 @@ for li = 1:nlinks links{1,li} = valuei(li).Target.Proxy.ID; linkattrs = valuei(li).Attributes; - linkattrkeys = keys(linkattrs); - linkattrvalues = values(linkattrs,"cell"); - % collapse one level of cells, as this may be due to - % a behavior of dictionary.values - if ~isempty(linkattrvalues) && all(cellfun(@iscell, linkattrvalues)) - linkattrvalues = [linkattrvalues{:}]; - end + [linkattrkeys, linkattrvalues] = processAttributes(linkattrs, true); links{2,li} = linkattrkeys; links{3,li} = linkattrvalues; end diff --git a/test/tlogs.m b/test/tlogs.m index fff5c9c..0f96682 100644 --- a/test/tlogs.m +++ b/test/tlogs.m @@ -286,11 +286,17 @@ function testAttributes(testCase) lp = opentelemetry.sdk.logs.LoggerProvider(); lg = getLogger(lp, "foo"); - attributes = dictionary(["stringscalar", "doublescalar", "int32scalar", "uint32scalar", ... - "int64scalar", "logicalscalar", "doublearray", "int32array", "uint32array", ... - "int64array", "logicalarray", "stringarray"], {"foo", 10, int32(10), uint32(20), ... - int64(35), false, [2 3; 4 5], int32(1:6), uint32((15:18).'), int64(reshape(1:4,2,1,2)), ... - [true false true], ["foo", "bar", "quux", "quz"]}); + attributes = dictionary(["stringscalar", "charrow", "singlescalar", ... + "doublescalar", "int8scalar", "uint16scalar", "int32scalar", "uint32scalar", ... + "int64scalar", "uint64scalar", "logicalscalar", "singlearray", ... + "doublearray", "int8array", "uint16array", "int32array", "uint32array", ... + "int64array", "uint64array", "logicalarray", "stringarray", ... + "cellstr", "duration"], {"foo", 'bar', single(5), 10, int8(1), ... + uint16(10), int32(10), uint32(20), int64(35), uint64(100), false, ... + single([1 2; 3 4]), [2 3; 4 5], int8(-2:3), uint16(1:5), ... + int32(1:6), uint32((15:18).'), int64(reshape(1:4,2,1,2)), ... + uint64([100 200; 300 400]), [true false true], ["foo", "bar", "quux", "quz"], ... + {'foo', 'bar', 'quux', 'quz'}, hours(2)}); emitLogRecord(lg, "warn", "bar", Attributes=attributes); % perform test comparisons @@ -300,74 +306,66 @@ function testAttributes(testCase) attrkeys = string({results{1}.resourceLogs.scopeLogs.logRecords.attributes.key}); % scalars - stringscidx = find(attrkeys == "stringscalar"); - verifyNotEmpty(testCase, stringscidx); - verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringscidx).value.stringValue), attributes{"stringscalar"}); - - doublescidx = find(attrkeys == "doublescalar"); - verifyNotEmpty(testCase, doublescidx); - verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(doublescidx).value.doubleValue, ... - attributes{"doublescalar"}); - - i32scidx = find(attrkeys == "int32scalar"); - verifyNotEmpty(testCase, i32scidx); - verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(i32scidx).value.intValue, ... - char(string(attributes{"int32scalar"}))); - - u32scidx = find(attrkeys == "uint32scalar"); - verifyNotEmpty(testCase, u32scidx); - verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(u32scidx).value.intValue, ... - char(string(attributes{"uint32scalar"}))); - - i64scidx = find(attrkeys == "int64scalar"); - verifyNotEmpty(testCase, i64scidx); - verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(i64scidx).value.intValue, ... - char(string(attributes{"int64scalar"}))); + % string and char + textscalars = ["stringscalar" "charrow"]; + for i = 1:length(textscalars) + stringscidx = find(attrkeys == textscalars(i)); + verifyNotEmpty(testCase, stringscidx); + verifyEqual(testCase, string(results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringscidx).value.stringValue), ... + string(attributes{textscalars(i)})); + end + + % floating point scalars + floatscalars = ["single" "double"] + "scalar"; + for i = 1:length(floatscalars) + floatscidx = find(attrkeys == floatscalars(i)); + verifyNotEmpty(testCase, floatscidx); + verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(floatscidx).value.doubleValue, ... + double(attributes{floatscalars(i)})); + end + + % integer scalars + intscalars = ["int8" "uint16" "int32" "uint32" "int64" "uint64"] + "scalar"; + for i = 1:length(intscalars) + intscidx = find(attrkeys == intscalars(i)); + verifyNotEmpty(testCase, intscidx); + verifyEqual(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(intscidx).value.intValue, ... + char(string(attributes{intscalars(i)}))); + end + % logical scalar logicalscidx = find(attrkeys == "logicalscalar"); verifyNotEmpty(testCase, logicalscidx); verifyFalse(testCase, results{1}.resourceLogs.scopeLogs.logRecords.attributes(logicalscidx).value.boolValue); % arrays - doublearidx = find(attrkeys == "doublearray"); - verifyNotEmpty(testCase, doublearidx); - verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(doublearidx).value.arrayValue.values.doubleValue], ... - reshape(attributes{"doublearray"}, 1, [])); - - doubleszidx = find(attrkeys == "doublearray.size"); - verifyNotEmpty(testCase, doubleszidx); - verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(doubleszidx).value.arrayValue.values.doubleValue], ... - size(attributes{"doublearray"})); - - i32aridx = find(attrkeys == "int32array"); - verifyNotEmpty(testCase, i32aridx); - verifyEqual(testCase, double(string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(i32aridx).value.arrayValue.values.intValue})), ... - double(reshape(attributes{"int32array"},1,[]))); - - i32szidx = find(attrkeys == "int32array.size"); - verifyNotEmpty(testCase, i32szidx); - verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(i32szidx).value.arrayValue.values.doubleValue], ... - size(attributes{"int32array"})); - - u32aridx = find(attrkeys == "uint32array"); - verifyNotEmpty(testCase, u32aridx); - verifyEqual(testCase, double(string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(u32aridx).value.arrayValue.values.intValue})), ... - double(reshape(attributes{"uint32array"},1,[]))); - - u32szidx = find(attrkeys == "uint32array.size"); - verifyNotEmpty(testCase, u32szidx); - verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(u32szidx).value.arrayValue.values.doubleValue], ... - size(attributes{"uint32array"})); - - i64aridx = find(attrkeys == "int64array"); - verifyNotEmpty(testCase, i64aridx); - verifyEqual(testCase, double(string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(i64aridx).value.arrayValue.values.intValue})), ... - double(reshape(attributes{"int64array"},1,[]))); - - i64szidx = find(attrkeys == "int64array.size"); - verifyNotEmpty(testCase, i64szidx); - verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(i64szidx).value.arrayValue.values.doubleValue], ... - size(attributes{"int64array"})); + % floating point arrays + floatarrays = ["single" "double"] + "array"; + for i = 1:length(floatarrays) + floataridx = find(attrkeys == floatarrays(i)); + verifyNotEmpty(testCase, floataridx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(floataridx).value.arrayValue.values.doubleValue], ... + double(reshape(attributes{floatarrays(i)}, 1, []))); + + floatszidx = find(attrkeys == floatarrays(i) + ".size"); + verifyNotEmpty(testCase, floatszidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(floatszidx).value.arrayValue.values.doubleValue], ... + size(attributes{floatarrays(i)})); + end + + % integer arrays + intarrays = ["int8" "uint16" "int32" "uint32" "int64" "uint64"] + "array"; + for i = 1:length(intarrays) + intaridx = find(attrkeys == intarrays(i)); + verifyNotEmpty(testCase, intaridx); + verifyEqual(testCase, double(string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(intaridx).value.arrayValue.values.intValue})), ... + double(reshape(attributes{intarrays(i)},1,[]))); + + intszidx = find(attrkeys == intarrays(i) + ".size"); + verifyNotEmpty(testCase, intszidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(intszidx).value.arrayValue.values.doubleValue], ... + size(attributes{intarrays(i)})); + end logicalaridx = find(attrkeys == "logicalarray"); verifyNotEmpty(testCase, logicalaridx); @@ -379,15 +377,23 @@ function testAttributes(testCase) verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(logicalszidx).value.arrayValue.values.doubleValue], ... size(attributes{"logicalarray"})); - stringaridx = find(attrkeys == "stringarray"); - verifyNotEmpty(testCase, stringaridx); - verifyEqual(testCase, string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringaridx).value.arrayValue.values.stringValue}), ... - attributes{"stringarray"}); + % text arrays + textarrays = ["stringarray" "cellstr"]; + for i = 1:length(textarrays) + stringaridx = find(attrkeys == textarrays(i)); + verifyNotEmpty(testCase, stringaridx); + verifyEqual(testCase, string({results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringaridx).value.arrayValue.values.stringValue}), ... + string(attributes{textarrays(i)})); + + stringszidx = find(attrkeys == textarrays(i) + ".size"); + verifyNotEmpty(testCase, stringszidx); + verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringszidx).value.arrayValue.values.doubleValue], ... + size(attributes{textarrays(i)})); + end - stringszidx = find(attrkeys == "stringarray.size"); - verifyNotEmpty(testCase, stringszidx); - verifyEqual(testCase, [results{1}.resourceLogs.scopeLogs.logRecords.attributes(stringszidx).value.arrayValue.values.doubleValue], ... - size(attributes{"stringarray"})); + % duration, not supported + durationidx = find(attrkeys == "duration"); + verifyEmpty(testCase, durationidx); end function testSeverityFunctions(testCase) diff --git a/test/ttrace.m b/test/ttrace.m index 62b8016..0849e53 100644 --- a/test/ttrace.m +++ b/test/ttrace.m @@ -293,11 +293,17 @@ function testAttributes(testCase) tp = opentelemetry.sdk.trace.TracerProvider(); tr = getTracer(tp, "tracer"); - attributes = dictionary(["stringscalar", "doublescalar", "int32scalar", "uint32scalar", ... - "int64scalar", "logicalscalar", "doublearray", "int32array", "uint32array", ... - "int64array", "logicalarray", "stringarray"], {"foo", 10, int32(10), uint32(20), ... - int64(35), false, [2 3; 4 5], int32(1:6), uint32((15:18).'), int64(reshape(1:4,2,1,2)), ... - [true false true], ["foo", "bar", "quux", "quz"]}); + attributes = dictionary(["stringscalar", "charrow", "singlescalar", "doublescalar", ... + "int8scalar", "uint16scalar", "int32scalar", "uint32scalar", "int64scalar", ... + "uint64scalar", "logicalscalar", "singlearray", "doublearray", ... + "int8array", "uint16array", "int32array", "uint32array", "int64array", ... + "uint64array", "logicalarray", "stringarray", "cellstr", "datetime"], ... + {"foo", 'bar', single(5), 10, ... + uint8(2), int16(100), int32(10), uint32(20), int64(35), ... + uint64(9999), false, single([1 2; 3 4]), [2 3; 4 5], int8(-2:2), ... + uint16(1:10), int32(1:6), uint32((15:18).'), int64(reshape(1:4,2,1,2)), ... + uint64([100 200; 300 400]), [true false true], ["foo", "bar", "quux", "quz"], ... + {'foo', 'bar', 'quux', 'quz'}, datetime("now")}); sp1 = startSpan(tr, "span", "Attributes", attributes); endSpan(sp1); @@ -307,75 +313,67 @@ function testAttributes(testCase) attrkeys = string({results{1}.resourceSpans.scopeSpans.spans.attributes.key}); % scalars - stringscidx = find(attrkeys == "stringscalar"); - verifyNotEmpty(testCase, stringscidx); - verifyEqual(testCase, string(results{1}.resourceSpans.scopeSpans.spans.attributes(stringscidx).value.stringValue), attributes{"stringscalar"}); - - doublescidx = find(attrkeys == "doublescalar"); - verifyNotEmpty(testCase, doublescidx); - verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.attributes(doublescidx).value.doubleValue, ... - attributes{"doublescalar"}); - - i32scidx = find(attrkeys == "int32scalar"); - verifyNotEmpty(testCase, i32scidx); - verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.attributes(i32scidx).value.intValue, ... - char(string(attributes{"int32scalar"}))); - - u32scidx = find(attrkeys == "uint32scalar"); - verifyNotEmpty(testCase, u32scidx); - verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.attributes(u32scidx).value.intValue, ... - char(string(attributes{"uint32scalar"}))); - - i64scidx = find(attrkeys == "int64scalar"); - verifyNotEmpty(testCase, i64scidx); - verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.attributes(i64scidx).value.intValue, ... - char(string(attributes{"int64scalar"}))); - + % string and char + textscalars = ["stringscalar" "charrow"]; + for i = 1:length(textscalars) + stringscidx = find(attrkeys == textscalars(i)); + verifyNotEmpty(testCase, stringscidx); + verifyEqual(testCase, string(results{1}.resourceSpans.scopeSpans.spans.attributes(stringscidx).value.stringValue), ... + string(attributes{textscalars(i)})); + end + + % floating point scalars + floatscalars = ["single" "double"] + "scalar"; + for i = 1:length(floatscalars) + floatscidx = find(attrkeys == floatscalars(i)); + verifyNotEmpty(testCase, floatscidx); + verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.attributes(floatscidx).value.doubleValue, ... + double(attributes{floatscalars(i)})); + end + + % integer scalars + intscalars = ["int8" "uint16" "int32" "uint32" "int64" "uint64"] + "scalar"; + for i = 1:length(intscalars) + intscidx = find(attrkeys == intscalars(i)); + verifyNotEmpty(testCase, intscidx); + verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.attributes(intscidx).value.intValue, ... + char(string(attributes{intscalars(i)}))); + end + + % logical scalar logicalscidx = find(attrkeys == "logicalscalar"); verifyNotEmpty(testCase, logicalscidx); verifyFalse(testCase, results{1}.resourceSpans.scopeSpans.spans.attributes(logicalscidx).value.boolValue); % arrays - doublearidx = find(attrkeys == "doublearray"); - verifyNotEmpty(testCase, doublearidx); - verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(doublearidx).value.arrayValue.values.doubleValue], ... - reshape(attributes{"doublearray"}, 1, [])); - - doubleszidx = find(attrkeys == "doublearray.size"); - verifyNotEmpty(testCase, doubleszidx); - verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(doubleszidx).value.arrayValue.values.doubleValue], ... - size(attributes{"doublearray"})); - - i32aridx = find(attrkeys == "int32array"); - verifyNotEmpty(testCase, i32aridx); - verifyEqual(testCase, double(string({results{1}.resourceSpans.scopeSpans.spans.attributes(i32aridx).value.arrayValue.values.intValue})), ... - double(reshape(attributes{"int32array"},1,[]))); - - i32szidx = find(attrkeys == "int32array.size"); - verifyNotEmpty(testCase, i32szidx); - verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(i32szidx).value.arrayValue.values.doubleValue], ... - size(attributes{"int32array"})); - - u32aridx = find(attrkeys == "uint32array"); - verifyNotEmpty(testCase, u32aridx); - verifyEqual(testCase, double(string({results{1}.resourceSpans.scopeSpans.spans.attributes(u32aridx).value.arrayValue.values.intValue})), ... - double(reshape(attributes{"uint32array"},1,[]))); - - u32szidx = find(attrkeys == "uint32array.size"); - verifyNotEmpty(testCase, u32szidx); - verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(u32szidx).value.arrayValue.values.doubleValue], ... - size(attributes{"uint32array"})); - - i64aridx = find(attrkeys == "int64array"); - verifyNotEmpty(testCase, i64aridx); - verifyEqual(testCase, double(string({results{1}.resourceSpans.scopeSpans.spans.attributes(i64aridx).value.arrayValue.values.intValue})), ... - double(reshape(attributes{"int64array"},1,[]))); - - i64szidx = find(attrkeys == "int64array.size"); - verifyNotEmpty(testCase, i64szidx); - verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(i64szidx).value.arrayValue.values.doubleValue], ... - size(attributes{"int64array"})); - + % floating point arrays + floatarrays = ["single" "double"] + "array"; + for i = 1:length(floatarrays) + floataridx = find(attrkeys == floatarrays(i)); + verifyNotEmpty(testCase, floataridx); + verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(floataridx).value.arrayValue.values.doubleValue], ... + double(reshape(attributes{floatarrays(i)}, 1, []))); + + floatszidx = find(attrkeys == floatarrays(i) + ".size"); + verifyNotEmpty(testCase, floatszidx); + verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(floatszidx).value.arrayValue.values.doubleValue], ... + size(attributes{floatarrays(i)})); + end + + % integer arrays + intarrays = ["int8" "uint16" "int32" "uint32" "int64" "uint64"] + "array"; + for i = 1:length(intarrays) + intaridx = find(attrkeys == intarrays(i)); + verifyNotEmpty(testCase, intaridx); + verifyEqual(testCase, double(string({results{1}.resourceSpans.scopeSpans.spans.attributes(intaridx).value.arrayValue.values.intValue})), ... + double(reshape(attributes{intarrays(i)},1,[]))); + + intszidx = find(attrkeys == intarrays(i) + ".size"); + verifyNotEmpty(testCase, intszidx); + verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(intszidx).value.arrayValue.values.doubleValue], ... + size(attributes{intarrays(i)})); + end + logicalaridx = find(attrkeys == "logicalarray"); verifyNotEmpty(testCase, logicalaridx); verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(logicalaridx).value.arrayValue.values.boolValue], ... @@ -386,15 +384,23 @@ function testAttributes(testCase) verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(logicalszidx).value.arrayValue.values.doubleValue], ... size(attributes{"logicalarray"})); - stringaridx = find(attrkeys == "stringarray"); - verifyNotEmpty(testCase, stringaridx); - verifyEqual(testCase, string({results{1}.resourceSpans.scopeSpans.spans.attributes(stringaridx).value.arrayValue.values.stringValue}), ... - attributes{"stringarray"}); - - stringszidx = find(attrkeys == "stringarray.size"); - verifyNotEmpty(testCase, stringszidx); - verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(stringszidx).value.arrayValue.values.doubleValue], ... - size(attributes{"stringarray"})); + % text arrays + textarrays = ["stringarray" "cellstr"]; + for i = 1:length(textarrays) + stringaridx = find(attrkeys == textarrays(i)); + verifyNotEmpty(testCase, stringaridx); + verifyEqual(testCase, string({results{1}.resourceSpans.scopeSpans.spans.attributes(stringaridx).value.arrayValue.values.stringValue}), ... + string(attributes{textarrays(i)})); + + stringszidx = find(attrkeys == textarrays(i) + ".size"); + verifyNotEmpty(testCase, stringszidx); + verifyEqual(testCase, [results{1}.resourceSpans.scopeSpans.spans.attributes(stringszidx).value.arrayValue.values.doubleValue], ... + size(attributes{textarrays(i)})); + end + + % datetime, not supported + datetimeidx = find(attrkeys == "datetime"); + verifyEmpty(testCase, datetimeidx); end function testSetAttributes(testCase) From 72aa5a37c206d958a65d9f6714a373a3d17edf81 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Fri, 19 Apr 2024 11:10:19 -0400 Subject: [PATCH 10/18] Add a warning to makeCurrent without output, resolves #92 --- api/trace/+opentelemetry/+trace/Span.m | 5 +++++ test/ttrace.m | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/api/trace/+opentelemetry/+trace/Span.m b/api/trace/+opentelemetry/+trace/Span.m index 2e0d9db..24a7e5e 100644 --- a/api/trace/+opentelemetry/+trace/Span.m +++ b/api/trace/+opentelemetry/+trace/Span.m @@ -68,6 +68,11 @@ function endSpan(obj, endtime) % See also OPENTELEMETRY.CONTEXT.CONTEXT, % OPENTELEMETRY.GETCURRENTCONTEXT, OPENTELEMETRY.TRACE.SCOPE + % return a warning if no output specified + if nargout == 0 + warning("opentelemetry:trace:Span:makeCurrent:NoOutputSpecified", ... + "Calling makeCurrent without specifying an output has no effect.") + end id = obj.Proxy.makeCurrent(); scopeproxy = libmexclass.proxy.Proxy("Name", ... "libmexclass.opentelemetry.ScopeProxy", "ID", id); diff --git a/test/ttrace.m b/test/ttrace.m index 0849e53..a1e90d9 100644 --- a/test/ttrace.m +++ b/test/ttrace.m @@ -191,6 +191,15 @@ function testExplicitParent(testCase) verifyEqual(testCase, results{1}.resourceSpans.scopeSpans.spans.traceId, results{2}.resourceSpans.scopeSpans.spans.traceId); end + function testMakeCurrentNoOutput(testCase) + % testMakeCurrentNoOutput: calling makeCurrent without an + % output should return a warning + tp = opentelemetry.sdk.trace.TracerProvider(); + tr = getTracer(tp, "foo"); + sp = startSpan(tr, "bar"); + verifyWarning(testCase, @()makeCurrent(sp), "opentelemetry:trace:Span:makeCurrent:NoOutputSpecified"); + end + function testSpanKind(testCase) % testSpanKind: specifying SpanKind From 59486a50efe80305f66c2e4a27306ef543036831 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Fri, 19 Apr 2024 11:12:54 -0400 Subject: [PATCH 11/18] build with grpc exporter --- .github/workflows/build_and_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index ad9277e..7cd3385 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -24,7 +24,7 @@ jobs: - name: Build OpenTelemetry-Matlab working-directory: opentelemetry-matlab run: | - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_GRPC=ON -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Run tests env: @@ -54,7 +54,7 @@ jobs: shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 - cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_GRPC=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Run tests env: @@ -82,7 +82,7 @@ jobs: - name: Build OpenTelemetry-Matlab working-directory: opentelemetry-matlab run: | - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_GRPC=ON -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Run tests env: From 38899d7d794a76fdb2798ebd0b12c87530d789b1 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Fri, 19 Apr 2024 13:34:41 -0400 Subject: [PATCH 12/18] Revert from building with grpc, leave out examples when building on Mac --- .github/workflows/build_and_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 7cd3385..2512cf5 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -24,7 +24,7 @@ jobs: - name: Build OpenTelemetry-Matlab working-directory: opentelemetry-matlab run: | - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_GRPC=ON -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Run tests env: @@ -54,7 +54,7 @@ jobs: shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 - cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_GRPC=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER="cl.exe" -DCMAKE_C_COMPILER="cl.exe" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Run tests env: @@ -82,7 +82,7 @@ jobs: - name: Build OpenTelemetry-Matlab working-directory: opentelemetry-matlab run: | - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DWITH_OTLP_GRPC=ON -DWITH_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ env.OPENTELEMETRY_MATLAB_INSTALL }} cmake --build build --config Release --target install - name: Run tests env: From f10c049c9db5c172cc81287f9c7bb19aa67d50ef Mon Sep 17 00:00:00 2001 From: duncanpo Date: Mon, 22 Apr 2024 11:25:25 -0400 Subject: [PATCH 13/18] Update OpenTelemetry-cpp to 1.15.0, closes #109 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cfe86f..bbaf23f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,7 +171,7 @@ if(NOT DEFINED OTEL_CPP_INSTALLED_DIR) include(ExternalProject) set(OTEL_CPP_PROJECT_NAME opentelemetry-cpp) set(OTEL_CPP_GIT_REPOSITORY "https://github.com/open-telemetry/opentelemetry-cpp.git") - set(OTEL_CPP_GIT_TAG "a799f4a") + set(OTEL_CPP_GIT_TAG "054b0dc") set(OTEL_CPP_CXX_STANDARD 14) From a4ba36ff5110e5870d11912a34a4b7c5c245f842 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Mon, 22 Apr 2024 11:27:11 -0400 Subject: [PATCH 14/18] Bump up CMake minimum from 3.7 to 3.9, closes #111 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bbaf23f..d0975a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.7.0) +cmake_minimum_required(VERSION 3.9.0) cmake_policy(SET CMP0074 NEW) From 689d77201d0366e62f0d6049ffa91e127176447f Mon Sep 17 00:00:00 2001 From: duncanpo Date: Mon, 22 Apr 2024 13:10:50 -0400 Subject: [PATCH 15/18] Update vcpkg, closes #110 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0975a3..8777ada 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ else() set(VCPKG_FETCH_CONTENT_NAME vcpkg) set(VCPKG_GIT_REPOSITORY "https://github.com/microsoft/vcpkg.git") - set(VCPKG_GIT_TAG "9edb1b8") + set(VCPKG_GIT_TAG "fba75d0") FetchContent_Declare( ${VCPKG_FETCH_CONTENT_NAME} GIT_REPOSITORY ${VCPKG_GIT_REPOSITORY} From f3bd36891e64a76df1f59b7deadc978013dd0f9c Mon Sep 17 00:00:00 2001 From: duncanpo Date: Mon, 22 Apr 2024 13:42:46 -0400 Subject: [PATCH 16/18] Update default export format, closes #108 --- .../+exporters/+otlp/OtlpHttpLogRecordExporter.m | 2 +- .../+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m | 4 ++-- .../+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m index f3bd840..489e4dc 100644 --- a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpLogRecordExporter.m @@ -7,7 +7,7 @@ properties Endpoint (1,1) string = "http://localhost:4318/v1/logs" % Export destination - Format (1,1) string = "JSON" % Data format, JSON or binary + Format (1,1) string = "binary" % Data format, JSON or binary JsonBytesMapping (1,1) string = "hexId" % What to convert JSON bytes to UseJsonName (1,1) logical = false % Whether to use JSON name of protobuf field to set the key of JSON Timeout (1,1) duration = seconds(10) % Maximum time above which exports will abort diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m index 7890c9c..de64aec 100644 --- a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpMetricExporter.m @@ -3,11 +3,11 @@ % HTTP. By default, it exports to the default address of the OpenTelemetry % Collector. -% Copyright 2023 The MathWorks, Inc. +% Copyright 2023-2024 The MathWorks, Inc. properties Endpoint (1,1) string = "http://localhost:4318/v1/metrics" % Export destination - Format (1,1) string = "JSON" % Data format, JSON or binary + Format (1,1) string = "binary" % Data format, JSON or binary JsonBytesMapping (1,1) string = "hexId" % What to convert JSON bytes to UseJsonName (1,1) logical = false % Whether to use JSON name of protobuf field to set the key of JSON Timeout (1,1) duration = seconds(10) % Maximum time above which exports will abort diff --git a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m index 9c10787..b127788 100644 --- a/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m +++ b/exporters/otlp/+opentelemetry/+exporters/+otlp/OtlpHttpSpanExporter.m @@ -3,11 +3,11 @@ % HTTP. By default, it exports to the default address of the OpenTelemetry % Collector. -% Copyright 2023 The MathWorks, Inc. +% Copyright 2023-2024 The MathWorks, Inc. properties Endpoint (1,1) string = "http://localhost:4318/v1/traces" % Export destination - Format (1,1) string = "JSON" % Data format, JSON or binary + Format (1,1) string = "binary" % Data format, JSON or binary JsonBytesMapping (1,1) string = "hexId" % What to convert JSON bytes to UseJsonName (1,1) logical = false % Whether to use JSON name of protobuf field to set the key of JSON Timeout (1,1) duration = seconds(10) % Maximum time above which exports will abort From f6919c14a883c07652760e4e1a2a8992e463346c Mon Sep 17 00:00:00 2001 From: duncanpo Date: Tue, 23 Apr 2024 10:03:58 -0400 Subject: [PATCH 17/18] add an example for logs, closes #112 --- examples/logs/README.md | 11 +++++++ examples/logs/logs_example.m | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 examples/logs/README.md create mode 100644 examples/logs/logs_example.m diff --git a/examples/logs/README.md b/examples/logs/README.md new file mode 100644 index 0000000..d745614 --- /dev/null +++ b/examples/logs/README.md @@ -0,0 +1,11 @@ +# Logs Example +This example shows how to emit logs in MATLAB code using OpenTelemetry. OpenTelemetry-Matlab supports logging either through its front-end API, or using existing logging functions together with an appender. This example shows logging through front-end API. Currently, there is not yet any appenders available in the package. +* At the beginning of the first run, initialization is necessary to create and store a global logger provider. +* At the beginning of each function, the global logger provider is used to create a logger. +* Use the created logger to create and emit logs. + +## Running the Example +1. Start an instance of [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector). +2. Start MATLAB. +3. Ensure the installation directory of OpenTelemetry-matlab is on the MATLAB path. +4. Run logs_example. diff --git a/examples/logs/logs_example.m b/examples/logs/logs_example.m new file mode 100644 index 0000000..9d7e8af --- /dev/null +++ b/examples/logs/logs_example.m @@ -0,0 +1,59 @@ +function yf = logs_example +% This example creates and emits logs from MATLAB code that fits a line +% through a cluster of data points. It create a log record for each +% function call, and it also captures the parameters of the best fit line. + +% Copyright 2024 The MathWorks, Inc. + +% initialize logging during first run +runOnce(@initLogger); + +% create a logger and start emitting logs +lg = opentelemetry.logs.getLogger("logs_example"); +info(lg, "logs_example"); + +[x, y] = generate_data(); +yf = best_fit_line(x,y); +end + +function [x, y] = generate_data +% generate some random data +lg = opentelemetry.logs.getLogger("logs_example"); +info(lg, "generate_data"); + +a = 1.5; +b = 0.8; +sigma = 5; +x = 1:100; +y = a * x + b + sigma * randn(1, 100); +end + +function yf = best_fit_line(x, y) +% fit a line through points defined by inputs x and y +lg = opentelemetry.logs.getLogger("logs_example"); +info(lg, "best_fit_line"); + +coefs = polyfit(x, y, 1); + +% capture the coefficients +info(lg, coefs); + +yf = polyval(coefs , x); +end + +function initLogger +% set up global LoggerProvider +resource = dictionary("service.name", "OpenTelemetry-Matlab_examples"); +lp = opentelemetry.sdk.logs.LoggerProvider(... + opentelemetry.sdk.logs.SimpleLogRecordProcessor, Resource=resource); +setLoggerProvider(lp); +end + +% This helper ensures the input function is only run once +function runOnce(fh) +persistent hasrun +if isempty(hasrun) + feval(fh); + hasrun = 1; +end +end From 6b2ea542bc018d66058dfe905405a346f30c9da8 Mon Sep 17 00:00:00 2001 From: duncanpo Date: Tue, 23 Apr 2024 10:04:55 -0400 Subject: [PATCH 18/18] Update version number --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index 9c6d629..bd8bf88 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -1.6.1 +1.7.0