├── air.toml ├── .github ├── .gitignore └── workflows │ ├── pkgdown.yaml │ ├── test-coverage.yaml │ ├── R-CMD-check.yaml │ ├── pr-commands.yaml │ └── rhub.yaml ├── configure.win ├── LICENSE ├── .aspell ├── otelsdk.rds └── defaults.R ├── .vscode ├── extensions.json ├── c_cpp_properties.json └── settings.json ├── src ├── vendor │ └── opentelemetry-cpp.tgz ├── extra │ └── cmake │ │ ├── R-4.3 │ │ ├── protobuf-options.cmake │ │ ├── protobuf-config.cmake │ │ ├── protobuf-targets-release.cmake │ │ └── protobuf-config-version.cmake │ │ └── R-4.4 │ │ ├── protobuf-options.cmake │ │ ├── protobuf-config.cmake │ │ ├── protobuf-targets-release.cmake │ │ └── protobuf-config-version.cmake ├── utils.c ├── cleancall.h ├── r-collector.c ├── status.proto ├── errors.c ├── otel_common_r.h ├── context.c └── otel_attributes.h ├── .covrignore ├── R ├── cleancall.R ├── onload.R ├── otelsdk-package.R ├── cache.R ├── class.R ├── glue.R ├── error.R ├── defaults.R ├── tracer-provider-stdout.R ├── utils.R ├── logger-provider-stdout.R ├── tracer.R ├── tracer-provider-file.R ├── logger-provider-file.R ├── meter-provider-stdout.R ├── tracer-provider-memory.R ├── record.R ├── meter-provider-file.R ├── meter-provider-memory.R ├── tracer-provider-http.R ├── collector.R ├── logger-provider-http.R ├── meter-provider-http.R ├── meter.R └── collapse.R ├── tests ├── testthat │ ├── _snaps │ │ ├── error.md │ │ ├── formatting.md │ │ ├── tracer-provider-memory.md │ │ ├── tracer-provider-stdout.md │ │ ├── spelling.md │ │ ├── current.md │ │ ├── meter-provider-memory.md │ │ ├── logger-c.md │ │ ├── tracer-provider-file.md │ │ ├── meter-provider-file.md │ │ ├── meter-sdk-cc.md │ │ ├── defaults.md │ │ ├── unsafe │ │ │ └── utils.md │ │ ├── safe │ │ │ └── utils.md │ │ ├── utils.md │ │ ├── meter-provider-stdout.md │ │ ├── glue.md │ │ ├── logger-provider-http.md │ │ ├── tracer-provider-http.md │ │ ├── tracer-provider-http-default.md │ │ ├── meter-provider-http.md │ │ ├── logger-provider-stdout.md │ │ ├── logger-sdk-cc.md │ │ ├── collector-cc.md │ │ ├── span.md │ │ └── logger.md │ ├── test-meter-provider-memory.R │ ├── test-tracer-provider-http-default.R │ ├── test-tracer-provider-memory.R │ ├── test-meter-sdk-cc.R │ ├── test-spelling.R │ ├── test-tracer-provider-http.R │ ├── test-current.R │ ├── test-class.R │ ├── test-meter-provider-stdout.R │ ├── test-formatting.R │ ├── test-tracer-provider-file.R │ ├── test-tracer-provider-stdout.R │ ├── test-error.R │ ├── test-meter-provider-http.R │ ├── test-meter-provider-file.R │ ├── test-attributes.R │ ├── test-logger-provider-http.R │ ├── test-defaults.R │ ├── test-logger-c.R │ ├── test-logger-provider-file.R │ ├── test-logger-provider-stdout.R │ ├── test-glue.R │ ├── test-meter.R │ ├── test-meter-c.R │ ├── test-tracer.R │ ├── helper-mock.R │ ├── test-logger-sdk-cc.R │ ├── test-collector-cc.R │ └── test-print.R └── testthat.R ├── cleanup ├── tools └── scripts │ ├── clean-aliases.R │ └── update-cpp.sh ├── codecov.yml ├── .gitignore ├── NEWS.md ├── inst └── WORDLIST ├── .Rbuildignore ├── man ├── filesizeoptions.Rd ├── timeintervaloptions.Rd ├── otelsdk-package.Rd ├── with_otel_record.Rd ├── tracer_provider_memory.Rd ├── logger_provider_stdstream.Rd ├── tracer_provider_stdstream.Rd ├── meter_provider_stdstream.Rd └── meter_provider_memory.Rd ├── LICENSE.md ├── NAMESPACE ├── DESCRIPTION ├── _pkgdown.yml ├── README.Rmd └── README.md /air.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /configure.win: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | sh ./configure 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2025 2 | COPYRIGHT HOLDER: opentelemetry authors 3 | -------------------------------------------------------------------------------- /.aspell/otelsdk.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/otelsdk/HEAD/.aspell/otelsdk.rds -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Posit.air-vscode" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/vendor/opentelemetry-cpp.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/otelsdk/HEAD/src/vendor/opentelemetry-cpp.tgz -------------------------------------------------------------------------------- /.covrignore: -------------------------------------------------------------------------------- 1 | src/cpp 2 | src/install 3 | src/cleancall.c 4 | src/errors.c 5 | R/collapse.R 6 | R/docs.R 7 | R/friendly-type.R 8 | -------------------------------------------------------------------------------- /R/cleancall.R: -------------------------------------------------------------------------------- 1 | ccall <- call_with_cleanup <- function(ptr, ...) { 2 | .Call(cleancall_call, pairlist(ptr, ...), parent.frame()) 3 | } 4 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/error.md: -------------------------------------------------------------------------------- 1 | # cnd 2 | 3 | Code 4 | boo() 5 | Output 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/testthat/test-meter-provider-memory.R: -------------------------------------------------------------------------------- 1 | test_that("meter_provider_memory_options", { 2 | expect_snapshot({ 3 | meter_provider_memory_options() 4 | }) 5 | }) 6 | -------------------------------------------------------------------------------- /tests/testthat/test-tracer-provider-http-default.R: -------------------------------------------------------------------------------- 1 | test_that("HTTP tracer provider defaults", { 2 | expect_snapshot({ 3 | tracer_provider_http$options() 4 | }) 5 | }) 6 | -------------------------------------------------------------------------------- /tests/testthat/test-tracer-provider-memory.R: -------------------------------------------------------------------------------- 1 | test_that("tracer_provider_memory_options", { 2 | expect_snapshot({ 3 | tracer_provider_memory_options() 4 | }) 5 | }) 6 | -------------------------------------------------------------------------------- /.aspell/defaults.R: -------------------------------------------------------------------------------- 1 | Rd_files <- vignettes <- R_files <- description <- list( 2 | encoding = "UTF-8", 3 | language = "en", 4 | dictionaries = c("en_stats", "otelsdk") 5 | ) 6 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/formatting.md: -------------------------------------------------------------------------------- 1 | # code formatting 2 | 3 | Code 4 | invisible(processx::run("air", c("format", "--check", pkg), echo = TRUE, 5 | error_on_status = FALSE)) 6 | 7 | -------------------------------------------------------------------------------- /cleanup: -------------------------------------------------------------------------------- 1 | #' !/usr/bin/env sh 2 | 3 | rm -f src/Makevars src/statuc.pb.* \ 4 | src/extra/cmake/R-4.4/protobuf-targets.cmake \ 5 | src/extra/cmake/R-4.3/protobuf-targets.cmake \ 6 | src/otelcpplib 7 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/tracer-provider-memory.md: -------------------------------------------------------------------------------- 1 | # tracer_provider_memory_options 2 | 3 | Code 4 | tracer_provider_memory_options() 5 | Output 6 | $buffer_size 7 | [1] 100 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/tracer-provider-stdout.md: -------------------------------------------------------------------------------- 1 | # tracer_provider_stdstream_options 2 | 3 | Code 4 | tracer_provider_stdstream_options() 5 | Output 6 | $output 7 | [1] "stdout" 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/testthat/test-meter-sdk-cc.R: -------------------------------------------------------------------------------- 1 | test_that("otel_meter_provider_file_options_defaults_", { 2 | expect_snapshot({ 3 | meter_provider_file$options() 4 | }) 5 | }) 6 | 7 | test_that("otel_meter_provider_memory_get_metrics_", { 8 | # 9 | }) 10 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/spelling.md: -------------------------------------------------------------------------------- 1 | # spelling 2 | 3 | Code 4 | spelling::spell_check_package(pkg) 5 | Message 6 | DESCRIPTION does not contain 'Language' field. Defaulting to 'en-US'. 7 | Output 8 | No spelling errors found. 9 | 10 | -------------------------------------------------------------------------------- /tools/scripts/clean-aliases.R: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | mfns <- c( 4 | "man/environmentvariables.Rd" 5 | ) 6 | 7 | for (fn in mfns) { 8 | lns <- readLines(fn) 9 | flt <- grep("^\\\\alias[{]OTEL_", lns, value = TRUE, invert = TRUE) 10 | writeLines(flt, fn) 11 | } 12 | -------------------------------------------------------------------------------- /R/onload.R: -------------------------------------------------------------------------------- 1 | the <- new.env(parent = emptyenv()) 2 | 3 | .onLoad <- function(libname, pkgname) { 4 | the$span_kinds <- span_kinds 5 | the$span_status_codes <- span_status_codes 6 | the$default_resource_attributes <- default_resource_attributes() 7 | ccall(otel_init_constants, the) 8 | } 9 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/current.md: -------------------------------------------------------------------------------- 1 | # env vars are current for otel spec 2 | 3 | Code 4 | gh::gh("https://api.github.com/repos/{owner}/{repo}/releases/latest", owner = "open-telemetry", 5 | repo = "opentelemetry-specification")$tag_name 6 | Output 7 | [1] "v1.49.0" 8 | 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /R/otelsdk-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @aliases otelsdk-package 3 | #' @importFrom otel start_span 4 | #' @return Not applicable. 5 | #' @examples 6 | #' # Run your R script with OpenTelemetry tracing: 7 | #' # OTEL_TRACER_EXPORTER=otlp R -f myapp.R 8 | "_PACKAGE" 9 | 10 | #' @useDynLib otelsdk, .registration = TRUE 11 | NULL 12 | -------------------------------------------------------------------------------- /src/extra/cmake/R-4.3/protobuf-options.cmake: -------------------------------------------------------------------------------- 1 | # Verbose output 2 | option(protobuf_VERBOSE "Enable for verbose output" OFF) 3 | mark_as_advanced(protobuf_VERBOSE) 4 | 5 | # FindProtobuf module compatible 6 | option(protobuf_MODULE_COMPATIBLE "CMake built-in FindProtobuf.cmake module compatible" OFF) 7 | mark_as_advanced(protobuf_MODULE_COMPATIBLE) 8 | -------------------------------------------------------------------------------- /src/extra/cmake/R-4.4/protobuf-options.cmake: -------------------------------------------------------------------------------- 1 | # Verbose output 2 | option(protobuf_VERBOSE "Enable for verbose output" OFF) 3 | mark_as_advanced(protobuf_VERBOSE) 4 | 5 | # FindProtobuf module compatible 6 | option(protobuf_MODULE_COMPATIBLE "CMake built-in FindProtobuf.cmake module compatible" OFF) 7 | mark_as_advanced(protobuf_MODULE_COMPATIBLE) 8 | -------------------------------------------------------------------------------- /tests/testthat/test-spelling.R: -------------------------------------------------------------------------------- 1 | test_that("spelling", { 2 | skip_on_cran() 3 | skip_on_covr() 4 | 5 | pkg <- test_path("../../") 6 | if (!file.exists(file.path(pkg, "DESCRIPTION"))) { 7 | pkg <- file.path(pkg, "00_pkg_src", .packageName) 8 | } 9 | 10 | expect_snapshot({ 11 | spelling::spell_check_package(pkg) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /src/*.o 2 | /src/*.so 3 | /src/*.a 4 | /src/*.ax 5 | /src/*.gcda 6 | /src/*.gcno 7 | /src/*.gcov 8 | /src/symbols.rds 9 | /src/Makevars 10 | /src/cpp 11 | /src/install 12 | /README.html 13 | /src/extra/cmake/R-4.*/protobuf-targets.cmake 14 | /src/otelcpplib 15 | docs 16 | /tests/testthat/results.rds 17 | /dev-lib 18 | /.dev 19 | /revdep 20 | -------------------------------------------------------------------------------- /tests/testthat/test-tracer-provider-http.R: -------------------------------------------------------------------------------- 1 | test_that("tracer_provider_http", { 2 | tracer_provider <- tracer_provider_http_new() 3 | tracer <- tracer_provider$get_tracer("mytracer") 4 | tracer$flush() 5 | expect_true(TRUE) 6 | }) 7 | 8 | test_that("HTTP tracer provider defaults", { 9 | expect_snapshot({ 10 | tracer_provider_http_options() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(otelsdk) 3 | 4 | if (Sys.getenv("NOT_CRAN") != "") { 5 | if (requireNamespace("testthatlabs", quietly = TRUE)) { 6 | test_check( 7 | "otelsdk", 8 | reporter = asNamespace("testthatlabs")$non_interactive_reporter$new( 9 | "otelsdk" 10 | ) 11 | ) 12 | } else { 13 | test_check("otelsdk") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /R/cache.R: -------------------------------------------------------------------------------- 1 | the <- new.env(parent = emptyenv()) 2 | the$span_id_size <- NULL 3 | the$trace_id_size <- NULL 4 | 5 | span_id_size <- function() { 6 | the[["span_id_size"]] %||% 7 | (assign("span_id_size", ccall(otel_span_id_size), envir = the)) 8 | } 9 | 10 | trace_id_size <- function() { 11 | the[["trace_id_size"]] %||% 12 | (assign("trace_id_size", ccall(otel_trace_id_size), envir = the)) 13 | } 14 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/meter-provider-memory.md: -------------------------------------------------------------------------------- 1 | # meter_provider_memory_options 2 | 3 | Code 4 | meter_provider_memory_options() 5 | Output 6 | $buffer_size 7 | [1] 100 8 | 9 | $export_interval 10 | [1] 60000 11 | 12 | $export_timeout 13 | [1] 30000 14 | 15 | $aggregation_temporality 16 | cumulative 17 | 2 18 | 19 | 20 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # otelsdk (development version) 2 | 3 | # otelsdk 0.2.2 4 | 5 | * No user visible changes. 6 | 7 | # otelsdk 0.2.1 8 | 9 | * otelsdk now compiles on macOS if `cmake` was installed from the installer 10 | and is not on the `PATH`. 11 | 12 | * Documentation update for the new Otel 1.49.0 specification. 13 | The new `OTEL_ENTITIES` environment variable is not supported yet. 14 | 15 | # otelsdk 0.2.0 16 | 17 | First release on CRAN. 18 | -------------------------------------------------------------------------------- /R/class.R: -------------------------------------------------------------------------------- 1 | new_object <- function(cls, ...) { 2 | bind_env <- new.env(parent = emptyenv()) 3 | encl_env <- new.env(parent = parent.frame()) 4 | encl_env[["self"]] <- bind_env 5 | mbs <- list(...) 6 | # TODO: check named 7 | for (i in seq_along(mbs)) { 8 | if (is.function(mbs[[i]])) { 9 | environment(mbs[[i]]) <- encl_env 10 | } 11 | bind_env[[names(mbs)[i]]] <- mbs[[i]] 12 | } 13 | class(bind_env) <- cls 14 | bind_env 15 | } 16 | -------------------------------------------------------------------------------- /tests/testthat/test-current.R: -------------------------------------------------------------------------------- 1 | test_that("env vars are current for otel spec", { 2 | skip_on_cran() 3 | # If this fails, then you need to update docs.R, the version number 4 | # there, and also the support status of the new or modified enviromnent 5 | # variables, if any. 6 | expect_snapshot( 7 | gh::gh( 8 | "https://api.github.com/repos/{owner}/{repo}/releases/latest", 9 | owner = "open-telemetry", 10 | repo = "opentelemetry-specification" 11 | )$tag_name 12 | ) 13 | }) 14 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${default}", 7 | "${workspaceFolder}/src/cpp/sdk/include" 8 | ], 9 | "defines": [], 10 | "compilerPath": "/usr/bin/clang", 11 | "cStandard": "c17", 12 | "cppStandard": "c++17", 13 | "intelliSenseMode": "macos-clang-arm64" 14 | } 15 | ], 16 | "version": 4 17 | } -------------------------------------------------------------------------------- /tests/testthat/_snaps/logger-c.md: -------------------------------------------------------------------------------- 1 | # otel_logger_provider_file_options_defaults 2 | 3 | Code 4 | logger_provider_file$options() 5 | Output 6 | $file_pattern 7 | [1] "logs-%N.jsonl" 8 | 9 | $alias_pattern 10 | [1] "logs-latest.jsonl" 11 | 12 | $flush_interval 13 | [1] 30 14 | 15 | $flush_count 16 | [1] 256 17 | 18 | $file_size 19 | [1] 20971520 20 | 21 | $rotate_size 22 | [1] 10 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/tracer-provider-file.md: -------------------------------------------------------------------------------- 1 | # tracer_provider_file$options 2 | 3 | Code 4 | tracer_provider_file$options() 5 | Output 6 | $file_pattern 7 | [1] "trace-%N.jsonl" 8 | 9 | $alias_pattern 10 | [1] "trace-latest.jsonl" 11 | 12 | $flush_interval 13 | [1] 30 14 | 15 | $flush_count 16 | [1] 256 17 | 18 | $file_size 19 | [1] 20971520 20 | 21 | $rotate_size 22 | [1] 10 23 | 24 | 25 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | CMD 2 | CRAN's 3 | CRAN’s 4 | Codecov 5 | GiB 6 | Grafana 7 | Homebrew 8 | JSONL 9 | Jaeger 10 | KiB 11 | Logfire 12 | MiB 13 | OTLP 14 | OpenTelemetry 15 | PBC 16 | PiB 17 | Pydantic 18 | ROR 19 | Rtools 20 | SDK 21 | SDKs 22 | SSL 23 | SigNoz 24 | TiB 25 | TLS 26 | UI 27 | auth 28 | backoff 29 | cpp 30 | dplyr 31 | funder 32 | github 33 | globbing 34 | http 35 | https 36 | instrance 37 | json 38 | lifecycle 39 | macOS 40 | observability 41 | opentelemetry 42 | otel 43 | protobuf 44 | signoz 45 | stdstream 46 | temporality 47 | tui 48 | unlink 49 | -------------------------------------------------------------------------------- /tests/testthat/test-class.R: -------------------------------------------------------------------------------- 1 | test_that("new_object", { 2 | obj <- new_object( 3 | "myclass", 4 | method = function() "mymethod", 5 | getdata = function() self$data, 6 | data = "data" 7 | ) 8 | 9 | expect_true(is.environment(obj)) 10 | expect_equal(class(obj), "myclass") 11 | expect_equal(parent.env(obj), emptyenv()) 12 | expect_equal( 13 | parent.env(environment(obj$method)), 14 | environment() 15 | ) 16 | expect_equal(obj$method(), "mymethod") 17 | expect_equal(obj$data, "data") 18 | expect_equal(obj$getdata(), "data") 19 | }) 20 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^src/.*[.]o$ 2 | ^src/.*[.]so$ 3 | ^src/.*[.]a$ 4 | ^src/.*[.]ax$ 5 | ^src/.*[.]gcda$ 6 | ^src/.*[.]gcno$ 7 | ^src/.*[.]gcov$ 8 | ^src/symbols[.]rds$ 9 | ^src/cpp$ 10 | ^src/install$ 11 | ^src/Makevars$ 12 | ^src/cpp/build$ 13 | ^LICENSE\.md$ 14 | ^\.github$ 15 | ^codecov\.yml$ 16 | ^\.covrignore$ 17 | ^README\.Rmd$ 18 | ^README\.html$ 19 | ^[\.]?air\.toml$ 20 | ^\.vscode$ 21 | ^src/extra/cmake/R-4[.]./protobuf-targets[.]cmake$ 22 | ^src/otelcpplib$ 23 | ^_pkgdown\.yml$ 24 | ^docs$ 25 | ^pkgdown$ 26 | ^tools$ 27 | ^tests/testthat/results[.]rds$ 28 | ^dev-lib$ 29 | ^CRAN-SUBMISSION$ 30 | ^[.]dev$ 31 | ^revdep$ 32 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/meter-provider-file.md: -------------------------------------------------------------------------------- 1 | # meter_provider_file$options 2 | 3 | Code 4 | meter_provider_file$options() 5 | Output 6 | $export_interval 7 | [1] 60000 8 | 9 | $export_timeout 10 | [1] 30000 11 | 12 | $file_pattern 13 | [1] "metrics-%N.jsonl" 14 | 15 | $alias_pattern 16 | [1] "metrics-latest.jsonl" 17 | 18 | $flush_interval 19 | [1] 30 20 | 21 | $flush_count 22 | [1] 256 23 | 24 | $file_size 25 | [1] 20971520 26 | 27 | $rotate_size 28 | [1] 10 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/meter-sdk-cc.md: -------------------------------------------------------------------------------- 1 | # otel_meter_provider_file_options_defaults_ 2 | 3 | Code 4 | meter_provider_file$options() 5 | Output 6 | $export_interval 7 | [1] 60000 8 | 9 | $export_timeout 10 | [1] 30000 11 | 12 | $file_pattern 13 | [1] "metrics-%N.jsonl" 14 | 15 | $alias_pattern 16 | [1] "metrics-latest.jsonl" 17 | 18 | $flush_interval 19 | [1] 30 20 | 21 | $flush_count 22 | [1] 256 23 | 24 | $file_size 25 | [1] 20971520 26 | 27 | $rotate_size 28 | [1] 10 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/testthat/test-meter-provider-stdout.R: -------------------------------------------------------------------------------- 1 | test_that("meter_provider_stdout", { 2 | skip("unreliable") 3 | tmp <- tempfile() 4 | on.exit(unlink(tmp), add = TRUE) 5 | mp <- meter_provider_stdstream_new(list(output = tmp)) 6 | on.exit(mp$shutdown(), add = TRUE) 7 | mtr <- mp$get_meter() 8 | ctr <- mtr$create_counter("ctr") 9 | ctr$add(1) 10 | ctr$add(10) 11 | mp$flush() 12 | lns <- readLines(tmp) 13 | expect_snapshot( 14 | writeLines(lns), 15 | transform = transform_meter_provider_file 16 | ) 17 | }) 18 | 19 | test_that("meter_provider_stdstream_options", { 20 | expect_snapshot({ 21 | meter_provider_stdstream_options() 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /man/filesizeoptions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docs.R 3 | \name{File Size Options} 4 | \alias{File Size Options} 5 | \title{File Size Options} 6 | \value{ 7 | Not applicable. 8 | } 9 | \description{ 10 | otel and otelsdk accept file size options in the following format: 11 | \itemize{ 12 | \item As a positive numeric scalar, interpreted as number of bytes. 13 | \item A string scalar with a positive number and a unit suffix. 14 | Possible units: B, KB, KiB, MB, MiB, GB, GiB, TB, TiB, PB, PiB. 15 | Units are case insensitive. 16 | } 17 | } 18 | \examples{ 19 | # Maximum output file size is 128 MiB: 20 | # OTEL_EXPORTER_OTLP_FILE_FILE_SIZE=128MiB 21 | } 22 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/defaults.md: -------------------------------------------------------------------------------- 1 | # span_kinds 2 | 3 | Code 4 | span_kinds 5 | Output 6 | default 7 | "internal" "server" "client" "producer" "consumer" 8 | 9 | # span_status_codes 10 | 11 | Code 12 | span_status_codes 13 | Output 14 | default 15 | "unset" "ok" "error" 16 | 17 | # default_resource_attributes 18 | 19 | Code 20 | da[["telemetry.sdk.language"]] 21 | Output 22 | [1] "R" 23 | Code 24 | da[["telemetry.sdk.name"]] 25 | Output 26 | [1] "opentelemetry" 27 | Code 28 | da[["process.runtime.name"]] 29 | Output 30 | [1] "R" 31 | 32 | -------------------------------------------------------------------------------- /src/extra/cmake/R-4.3/protobuf-config.cmake: -------------------------------------------------------------------------------- 1 | # User options 2 | include("${CMAKE_CURRENT_LIST_DIR}/protobuf-options.cmake") 3 | 4 | # Depend packages 5 | if(NOT ZLIB_FOUND) 6 | find_package(ZLIB) 7 | endif() 8 | if(NOT TARGET absl::strings) 9 | find_package(absl CONFIG) 10 | endif() 11 | if(NOT TARGET utf8_range) 12 | find_package(utf8_range CONFIG) 13 | endif() 14 | 15 | # Imported targets 16 | include("${CMAKE_CURRENT_LIST_DIR}/protobuf-targets.cmake") 17 | 18 | # protobuf-generate function 19 | include("${CMAKE_CURRENT_LIST_DIR}/protobuf-generate.cmake") 20 | 21 | # CMake FindProtobuf module compatible file 22 | if(protobuf_MODULE_COMPATIBLE) 23 | include("${CMAKE_CURRENT_LIST_DIR}/protobuf-module.cmake") 24 | endif() 25 | -------------------------------------------------------------------------------- /src/extra/cmake/R-4.4/protobuf-config.cmake: -------------------------------------------------------------------------------- 1 | # User options 2 | include("${CMAKE_CURRENT_LIST_DIR}/protobuf-options.cmake") 3 | 4 | # Depend packages 5 | if(NOT ZLIB_FOUND) 6 | find_package(ZLIB) 7 | endif() 8 | if(NOT TARGET absl::strings) 9 | find_package(absl CONFIG) 10 | endif() 11 | if(NOT TARGET utf8_range) 12 | find_package(utf8_range CONFIG) 13 | endif() 14 | 15 | # Imported targets 16 | include("${CMAKE_CURRENT_LIST_DIR}/protobuf-targets.cmake") 17 | 18 | # protobuf-generate function 19 | include("${CMAKE_CURRENT_LIST_DIR}/protobuf-generate.cmake") 20 | 21 | # CMake FindProtobuf module compatible file 22 | if(protobuf_MODULE_COMPATIBLE) 23 | include("${CMAKE_CURRENT_LIST_DIR}/protobuf-module.cmake") 24 | endif() 25 | -------------------------------------------------------------------------------- /tests/testthat/test-formatting.R: -------------------------------------------------------------------------------- 1 | test_that("code formatting", { 2 | skip_on_cran() 3 | skip_on_covr() 4 | # always run locally, but on the CI on on macOS 5 | if (Sys.getenv("CI") != "" && Sys.info()[["sysname"]] != "Darwin") { 6 | skip("Only run code formatting check on macOS") 7 | } 8 | 9 | pkg <- test_path("../../") 10 | if (!file.exists(file.path(pkg, "DESCRIPTION"))) { 11 | pkg <- file.path(pkg, "00_pkg_src", .packageName) 12 | } 13 | 14 | if (Sys.which("air") == "") { 15 | stop("Could not find air installation") 16 | } 17 | 18 | expect_snapshot(invisible(processx::run( 19 | "air", 20 | c("format", "--check", pkg), 21 | echo = TRUE, 22 | error_on_status = FALSE 23 | ))) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/testthat/test-tracer-provider-file.R: -------------------------------------------------------------------------------- 1 | test_that("tracer_provider_file", { 2 | tmp <- tempfile(fileext = "jsonl") 3 | on.exit(unlink(tmp), add = TRUE) 4 | withr::local_envvar( 5 | structure(tmp, names = file_exporter_traces_file_envvar) 6 | ) 7 | 8 | tp <- tracer_provider_file_new() 9 | trc <- tp$get_tracer() 10 | sp1 <- trc$start_local_active_span("s1") 11 | sp1$end() 12 | tp$flush() 13 | 14 | expect_true(file.exists(tmp)) 15 | spn <- jsonlite::fromJSON(tmp, simplifyVector = FALSE) 16 | expect_equal(spn$resourceSpans[[1]]$scopeSpans[[1]]$spans[[1]]$name, "s1") 17 | }) 18 | 19 | test_that("tracer_provider_file$options", { 20 | expect_snapshot({ 21 | tracer_provider_file$options() 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/testthat/test-tracer-provider-stdout.R: -------------------------------------------------------------------------------- 1 | test_that("tracer_provider_stdstream", { 2 | tracer_provider <- tracer_provider_stdstream_new() 3 | tracer <- tracer_provider$get_tracer("mytracer") 4 | expect_true(TRUE) 5 | }) 6 | 7 | test_that("writing to a file", { 8 | tmp <- tempfile() 9 | on.exit(unlink(tmp), add = TRUE) 10 | tp <- tracer_provider_stdstream_new(list(output = tmp)) 11 | trc <- tp$get_tracer() 12 | sp1 <- trc$start_local_active_span("testspan") 13 | sp1$end() 14 | tp$flush() 15 | 16 | lns <- readLines(tmp) 17 | expect_true(any(grepl("name\\s*:\\s*testspan", lns))) 18 | }) 19 | 20 | test_that("tracer_provider_stdstream_options", { 21 | expect_snapshot({ 22 | tracer_provider_stdstream_options() 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/unsafe/utils.md: -------------------------------------------------------------------------------- 1 | # get_current_error 2 | 3 | Code 4 | get_current_error() 5 | Output 6 | $tried 7 | [1] FALSE 8 | 9 | $success 10 | [1] NA 11 | 12 | $object 13 | NULL 14 | 15 | $error 16 | [1] "This version of otelsdk cannot get error messages. Make sure that you are using the latest version." 17 | 18 | 19 | --- 20 | 21 | Code 22 | err 23 | Output 24 | $tried 25 | [1] FALSE 26 | 27 | $success 28 | [1] NA 29 | 30 | $object 31 | NULL 32 | 33 | $error 34 | [1] "This version of otelsdk cannot get error messages. Make sure that you are using the latest version." 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/safe/utils.md: -------------------------------------------------------------------------------- 1 | # get_current_error 2 | 3 | Code 4 | get_current_error() 5 | Output 6 | $tried 7 | [1] TRUE 8 | 9 | $success 10 | [1] FALSE 11 | 12 | $object 13 | NULL 14 | 15 | $error 16 | [1] "Cannot find error message, this is possibly a bug in the otelsdk package. Make sure that you are using the latest version." 17 | 18 | 19 | --- 20 | 21 | Code 22 | err 23 | Output 24 | $tried 25 | [1] TRUE 26 | 27 | $success 28 | [1] FALSE 29 | 30 | $object 31 | NULL 32 | 33 | $error 34 | [1] "Cannot find error message, this is possibly a bug in the otelsdk package. Make sure that you are using the latest version." 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/testthat/test-error.R: -------------------------------------------------------------------------------- 1 | test_that("cnd", { 2 | one <- 1 3 | two <- 2 4 | boo <- function() { 5 | cnd("This is {one} {two}") 6 | } 7 | expect_snapshot(boo()) 8 | }) 9 | 10 | test_that("caller_arg", { 11 | # testted well in test-checks.R 12 | expect_true(TRUE) 13 | }) 14 | 15 | test_that("as_caller_arg", { 16 | # testted well in test-checks.R 17 | expect_true(TRUE) 18 | }) 19 | 20 | test_that("as.character.otel_caller_arg", { 21 | # testted well in test-checks.R 22 | expect_true(TRUE) 23 | }) 24 | 25 | test_that("caller_env", { 26 | # testted well in test-checks.R 27 | expect_true(TRUE) 28 | }) 29 | 30 | test_that("frame_get", { 31 | # special cases 32 | skip_on_cran() 33 | expect_null(frame_get(.GlobalEnv)) 34 | fake(frame_get, "evalq", function(...) list()) 35 | expect_null(frame_get(environment(), sys.frame)) 36 | }) 37 | -------------------------------------------------------------------------------- /tests/testthat/test-meter-provider-http.R: -------------------------------------------------------------------------------- 1 | test_that("meter_provider_http", { 2 | coll <- webfakes::local_app_process(collector_app()) 3 | withr::local_envvar(OTEL_EXPORTER_OTLP_ENDPOINT = coll$url()) 4 | mp <- meter_provider_http_new() 5 | on.exit(mp$shutdown(), add = TRUE) 6 | mtr <- mp$get_meter() 7 | ctr <- mtr$create_counter("ctr") 8 | ctr$add(1) 9 | ctr$add(10) 10 | mp$flush() 11 | 12 | cl_resp <- curl::curl_fetch_memory(coll$url("/metrics")) 13 | expect_equal(cl_resp$status_code, 200L) 14 | cl_mcs <- jsonlite::fromJSON( 15 | rawToChar(cl_resp$content), 16 | simplifyVector = FALSE 17 | )[[1]][[1]] 18 | mcs <- cl_mcs$scope_metrics[[1]]$metrics[[1]] 19 | expect_equal(mcs$name, "ctr") 20 | }) 21 | 22 | test_that("meter_provider_http_options", { 23 | expect_snapshot({ 24 | meter_provider_http_options() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/testthat/test-meter-provider-file.R: -------------------------------------------------------------------------------- 1 | test_that("meter_provider_file", { 2 | tmp <- tempfile(fileext = ".jsonl") 3 | tmp2 <- paste0(tmp, "-a") 4 | on.exit(unlink(c(tmp, tmp2)), add = TRUE) 5 | mp <- meter_provider_file_new( 6 | opts = list(file_pattern = tmp, alias_pattern = tmp2) 7 | ) 8 | on.exit(mp$shutdown(), add = TRUE) 9 | mtr <- mp$get_meter() 10 | ctr <- mtr$create_counter("ctr") 11 | ctr$add(1) 12 | ctr$add(10) 13 | mp$flush() 14 | mp$shutdown() 15 | expect_true(file.exists(tmp)) 16 | expect_true(file.exists(tmp2)) 17 | mtcs <- jsonlite::fromJSON(readLines(tmp)[1], simplifyVector = FALSE) 18 | md <- mtcs$resourceMetrics[[1]]$scopeMetrics[[1]]$metrics[[1]] 19 | expect_equal(md$name, "ctr") 20 | expect_equal(md$sum$dataPoints[[1]]$asDouble, 11) 21 | }) 22 | 23 | test_that("meter_provider_file$options", { 24 | expect_snapshot({ 25 | meter_provider_file$options() 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/utils.md: -------------------------------------------------------------------------------- 1 | # map_chr 2 | 3 | Code 4 | map_chr(1:3, sqrt) 5 | Condition 6 | Error in `vapply()`: 7 | ! values must be type 'character', 8 | but FUN(X[[1]]) result is type 'double' 9 | 10 | # get_current_error, failure 11 | 12 | Code 13 | get_current_error() 14 | Output 15 | $tried 16 | [1] TRUE 17 | 18 | $success 19 | [1] FALSE 20 | 21 | $object 22 | NULL 23 | 24 | $error 25 | [1] "Could not get the error message. Shucks." 26 | 27 | 28 | --- 29 | 30 | Code 31 | get_current_error() 32 | Output 33 | $tried 34 | [1] FALSE 35 | 36 | $success 37 | [1] NA 38 | 39 | $object 40 | NULL 41 | 42 | $error 43 | [1] "This version of otelsdk cannot get error messages. Make sure that you are using the latest version." 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests/testthat/test-attributes.R: -------------------------------------------------------------------------------- 1 | test_that("attribute types", { 2 | attr <- list( 3 | lgl1 = FALSE, 4 | lgl = c(TRUE, FALSE, TRUE), 5 | chr1 = "ok!", 6 | chr = c("a", "b", "c"), 7 | dbl1 = 1.0, 8 | dbl = c(1.0, 2.0), 9 | int1 = 2L, 10 | int = 2:4 11 | ) 12 | 13 | spns <- with_otel_record(function() { 14 | sp <- otel::start_local_active_span("test", attributes = attr) 15 | })[["traces"]] 16 | 17 | attr2 <- spns[["test"]]$attributes 18 | expect_identical(attr2$lgl1, attr$lgl1) 19 | expect_identical(attr2$lgl, attr$lgl) 20 | expect_identical(attr2$chr1, attr$chr1) 21 | expect_identical(attr2$chr, attr$chr) 22 | expect_identical(attr2$dbl1, attr$dbl1) 23 | expect_identical(attr2$dbl, attr$dbl) 24 | # int is internally converted to double because it is int64_t in otel 25 | expect_identical(attr2$int1, as.double(attr$int1)) 26 | expect_identical(attr2$int, as.double(attr$int)) 27 | }) 28 | -------------------------------------------------------------------------------- /R/glue.R: -------------------------------------------------------------------------------- 1 | # Compared to glue::glue(), these are fixed: 2 | # - .sep = "" 3 | # - .trim = TRUE 4 | # - .null = character() 5 | # - .literal = TRUE 6 | # - .comment = "" 7 | # 8 | # we also don't allow passing in data as arguments, and `text` is 9 | # a single argument, no need to `paste()` etc. 10 | 11 | glue <- function( 12 | text, 13 | .envir = parent.frame(), 14 | .transformer = identity_transformer, 15 | .open = "{", 16 | .close = "}", 17 | .cli = FALSE, 18 | .trim = TRUE 19 | ) { 20 | text <- paste0(text, collapse = "") 21 | 22 | if (.trim) { 23 | text <- trim(text) 24 | } 25 | 26 | f <- function(expr) { 27 | eval_func <- as.character(.transformer(expr, .envir) %||% character()) 28 | } 29 | 30 | res <- ccall(glue_, text, f, .open, .close, .cli) 31 | 32 | paste0(unlist(res), collapse = "") 33 | } 34 | 35 | identity_transformer <- function(text, envir) { 36 | eval(parse(text = text, keep.source = FALSE), envir) 37 | } 38 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/meter-provider-stdout.md: -------------------------------------------------------------------------------- 1 | # meter_provider_stdout 2 | 3 | Code 4 | writeLines(lns) 5 | Output 6 | { 7 | scope name : org.r-project.R 8 | schema url : 9 | version : 10 | start time : 11 | end time : 12 | instrument name : ctr 13 | description : 14 | unit : 15 | type : SumPointData 16 | value : 11 17 | attributes : 18 | resources : 19 | service.name: unknown_service 20 | telemetry.sdk.language: cpp 21 | telemetry.sdk.name: opentelemetry 22 | telemetry.sdk.version: 1.22.0 23 | } 24 | 25 | # meter_provider_stdstream_options 26 | 27 | Code 28 | meter_provider_stdstream_options() 29 | Output 30 | $output 31 | [1] "stdout" 32 | 33 | $export_interval 34 | [1] 60000 35 | 36 | $export_timeout 37 | [1] 30000 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/testthat/test-logger-provider-http.R: -------------------------------------------------------------------------------- 1 | test_that("logger_provider_http", { 2 | skip_on_cran() 3 | coll <- webfakes::local_app_process(collector_app()) 4 | withr::local_envvar(OTEL_EXPORTER_OTLP_ENDPOINT = coll$url()) 5 | lp <- logger_provider_http_new(opts = list(schedule_delay = 1)) 6 | # on.exit(lp$shutdown(), add = TRUE) 7 | lgr <- lp$get_logger() 8 | lgr$log("Test!") 9 | lp$flush() 10 | 11 | # TODO: handle batched logs better, query /logs multiple times 12 | Sys.sleep(0.2) 13 | cl_resp <- curl::curl_fetch_memory(coll$url("/logs")) 14 | expect_equal(cl_resp$status_code, 200L) 15 | cl_logs <- jsonlite::fromJSON( 16 | rawToChar(cl_resp$content), 17 | simplifyVector = FALSE 18 | )[[1]][[1]] 19 | lr <- cl_logs$scope_logs[[1]]$log_records[[1]] 20 | expect_equal(lr$trace_id, "") 21 | expect_equal(lr$span_id, "") 22 | expect_equal(lr$severity_text, "INFO") 23 | expect_equal(lr$body, "Test!") 24 | }) 25 | 26 | test_that("logger_provider_http_options", { 27 | expect_snapshot(logger_provider_http_options()) 28 | }) 29 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/glue.md: -------------------------------------------------------------------------------- 1 | # glue errors 2 | 3 | Code 4 | glue("{ no brace") 5 | Condition 6 | Error: 7 | ! Expecting '}' 8 | Code 9 | glue("{ 'no quote ", .cli = TRUE) 10 | Condition 11 | Error: 12 | ! Unterminated quote (') 13 | Code 14 | glue("{ \"no dquote ", .cli = TRUE) 15 | Condition 16 | Error: 17 | ! Unterminated quote (") 18 | Code 19 | glue("{ `no backtick ", .cli = TRUE) 20 | Condition 21 | Error: 22 | ! Unterminated quote (`) 23 | 24 | # trim 25 | 26 | Code 27 | trim("\nfoo") 28 | Output 29 | [1] "foo" 30 | 31 | --- 32 | 33 | Code 34 | trim("foo\n bar") 35 | Output 36 | [1] "foo\nbar" 37 | Code 38 | trim("foo\n bar\n ") 39 | Output 40 | [1] "foo\n bar" 41 | 42 | --- 43 | 44 | Code 45 | trim("foo\n bar\n \nbaz") 46 | Output 47 | [1] "foo\n bar\n \nbaz" 48 | 49 | --- 50 | 51 | Code 52 | trim("\n \ta\n \tb\n \t\n \tc") 53 | Output 54 | [1] "a\nb\n \t\nc" 55 | 56 | -------------------------------------------------------------------------------- /R/error.R: -------------------------------------------------------------------------------- 1 | cnd <- function( 2 | ..., 3 | class = NULL, 4 | call = caller_env(), 5 | .envir = parent.frame() 6 | ) { 7 | call <- frame_get(call, sys.call) 8 | structure( 9 | list(message = glue(..., .envir = .envir), call = call), 10 | class = c(class, "error", "condition") 11 | ) 12 | } 13 | 14 | caller_arg <- function(arg) { 15 | arg <- substitute(arg) 16 | expr <- do.call(substitute, list(arg), envir = caller_env()) 17 | structure(list(expr), class = "otel_caller_arg") 18 | } 19 | 20 | as_caller_arg <- function(x) { 21 | structure(list(x), class = "otel_caller_arg") 22 | } 23 | 24 | as.character.otel_caller_arg <- function(x, ...) { 25 | lbl <- format(x[[1]]) 26 | gsub("\n.*$", "...", lbl) 27 | } 28 | 29 | caller_env <- function(n = 1) { 30 | parent.frame(n + 1) 31 | } 32 | 33 | frame_get <- function(frame, accessor) { 34 | if (identical(frame, .GlobalEnv)) { 35 | return(NULL) 36 | } 37 | frames <- evalq(sys.frames(), frame) 38 | for (i in seq_along(frames)) { 39 | if (identical(frames[[i]], frame)) { 40 | return(accessor(i)) 41 | } 42 | } 43 | NULL 44 | } 45 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Rinternals.h" 5 | 6 | #include "otel_common.h" 7 | 8 | SEXP rf_get_list_element(SEXP list, const char *str) { 9 | SEXP elmt = R_NilValue; 10 | SEXP names = PROTECT(Rf_getAttrib(list, R_NamesSymbol)); 11 | R_xlen_t len = Rf_xlength(list); 12 | 13 | for (R_xlen_t i = 0; i < len; i++) { 14 | if (!strcmp(CHAR(STRING_ELT(names, i)), str)) { 15 | elmt = VECTOR_ELT(list, i); 16 | break; 17 | } 18 | } 19 | UNPROTECT(1); 20 | return elmt; 21 | } 22 | 23 | SEXP otel_fail(void) { 24 | Rf_error("from C"); 25 | return R_NilValue; 26 | } 27 | 28 | SEXP otel_span_kinds = NULL; 29 | SEXP otel_span_status_codes = NULL; 30 | 31 | SEXP otel_init_constants(SEXP env) { 32 | R_PreserveObject(env); 33 | otel_span_kinds = Rf_findVarInFrame(env, Rf_install("span_kinds")); 34 | otel_span_status_codes = 35 | Rf_findVarInFrame(env, Rf_install("span_status_codes")); 36 | return R_NilValue; 37 | } 38 | 39 | SEXP create_empty_xptr(void ) { 40 | SEXP xptr = R_MakeExternalPtr(NULL, R_NilValue, R_NilValue); 41 | return xptr; 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 opentelemetry authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/testthat/test-defaults.R: -------------------------------------------------------------------------------- 1 | test_that("span_kinds", { 2 | skip_on_cran() 3 | expect_snapshot(span_kinds) 4 | expect_equal(span_kinds, otel::span_kinds) 5 | }) 6 | 7 | test_that("span_status_codes", { 8 | skip_on_cran() 9 | expect_snapshot(span_status_codes) 10 | expect_equal(span_status_codes, otel::span_status_codes) 11 | }) 12 | 13 | test_that("default_resource_attributes", { 14 | da <- default_resource_attributes() 15 | expect_snapshot({ 16 | da[["telemetry.sdk.language"]] 17 | da[["telemetry.sdk.name"]] 18 | da[["process.runtime.name"]] 19 | }) 20 | expect_equal( 21 | da[["telemetry.sdk.version"]], 22 | as.character(utils::packageVersion("otelsdk")) 23 | ) 24 | expect_equal( 25 | da[["process.runtime.version"]], 26 | as.character(getRversion()) 27 | ) 28 | expect_equal( 29 | da[["process.runtime.description"]], 30 | R.version.string 31 | ) 32 | expect_equal( 33 | da[["process.pid"]], 34 | Sys.getpid() 35 | ) 36 | expect_equal( 37 | da[["process.owner"]], 38 | Sys.info()["user"] 39 | ) 40 | expect_equal( 41 | da[["os.type"]], 42 | tolower(Sys.info()['sysname']) 43 | ) 44 | }) 45 | -------------------------------------------------------------------------------- /man/timeintervaloptions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docs.R 3 | \name{Time Interval Options} 4 | \alias{Time Interval Options} 5 | \title{Time Interval Options} 6 | \value{ 7 | Not applicable. 8 | } 9 | \description{ 10 | otel and otelsdk accept time interval options in the following format: 11 | \itemize{ 12 | \item A \link[base:difftime]{base::difftime} object. 13 | \item A positive numeric scalar, interpreted as number of milliseconds. 14 | It may be fractional. 15 | \item A string scalar with a positive number and a time unit suffix. 16 | Possible time units: us (microseconds), ms (milliseconds), s (seconds), 17 | m (minutes), h (hours), d (days). 18 | } 19 | 20 | When the time interval is specified in an environment variable, it may be: 21 | \itemize{ 22 | \item A positive number, interpreted as number of milliseconds. 23 | It may be fractional. 24 | \item A positive number with a time unit suffix. 25 | Possible time units: us (microseconds), ms (milliseconds), s (seconds), 26 | m (minutes), h (hours), d (days). 27 | } 28 | } 29 | \examples{ 30 | # Write pending telemetry data to the output file every 5 seconds: 31 | # OTEL_EXPORTER_OTLP_FILE_FLUSH_INTERVAL=5s 32 | } 33 | -------------------------------------------------------------------------------- /tools/scripts/update-cpp.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | cd src/vendor 4 | rm -rf cpp cpp-old 5 | tar xzf opentelemetry-cpp.tgz 6 | git rm opentelemetry-cpp.tgz 7 | mv cpp cpp-old 8 | mkdir cpp 9 | 10 | CPP=../../../opentelemetry-cpp 11 | 12 | cp ${CPP}/CMakeLists.txt cpp/ 13 | cp ${CPP}/CMakeSettings.json cpp/ 14 | 15 | cp -r ${CPP}/cmake cpp/ 16 | 17 | mkdir -p cpp/exporters 18 | cp ${CPP}/exporters/CMakeLists.txt cpp/exporters/ 19 | cp -r ${CPP}/exporters/ostream cpp/exporters/ 20 | cp -r ${CPP}/exporters/memory cpp/exporters/ 21 | cp -r ${CPP}/exporters/otlp cpp/exporters/ 22 | 23 | cp -r ${CPP}/ext cpp/ 24 | 25 | mkdir -p cpp/third_party 26 | cp ${CPP}/third_party/BUILD cpp/third_party/ 27 | cp -r ${CPP}/third_party/nlohmann-json cpp/third_party/ 28 | rm cpp/third_party/nlohmann-json/CITATION.cff 29 | cp -r ${CPP}/third_party/opentelemetry-proto cpp/third_party/ 30 | 31 | mkdir -p cpp/sdk/include 32 | cp -r ${CPP}/sdk/include/opentelemetry cpp/sdk/include/ 33 | cp -r ${CPP}/sdk/src cpp/sdk/ 34 | cp -r ${CPP}/sdk/CMakeLists.txt cpp/sdk/ 35 | 36 | cp -r ${CPP}/api cpp/ 37 | 38 | find cpp -name test -type d | xargs rm -rf 39 | find cpp -name docs -type d | xargs rm -rf 40 | 41 | echo 'Check cpp and then run' 42 | echo 'tar czf opentelemetry-cpp.tgz --options gzip:compression-level=9 --no-xattrs cpp' 43 | -------------------------------------------------------------------------------- /tests/testthat/test-logger-c.R: -------------------------------------------------------------------------------- 1 | test_that("otel_create_logger_provider_stdstream", { 2 | tmp <- tempfile(fileext = ".txt") 3 | on.exit(unlink(tmp), add = TRUE) 4 | lp <- logger_provider_stdstream$new(list(output = tmp)) 5 | lgr <- lp$get_logger("test") 6 | lgr$log("Hello there!") 7 | lp$flush() 8 | expect_true(file.size(tmp) > 0) 9 | }) 10 | 11 | test_that("otel_create_logger_provider_http", { 12 | skip_on_cran() 13 | coll <- webfakes::local_app_process(collector_app()) 14 | withr::local_envvar(OTEL_EXPORTER_OTLP_ENDPOINT = coll$url()) 15 | lp <- logger_provider_http_new( 16 | opts = list( 17 | max_queue_size = 100, 18 | schedule_delay = 1, 19 | max_export_batch_size = 10 20 | ) 21 | ) 22 | lgr <- lp$get_logger("test") 23 | rm(lp, lgr) 24 | gc() 25 | gc() 26 | expect_true(TRUE) 27 | }) 28 | 29 | test_that("otel_create_logger_provider_file", { 30 | tmp <- tempfile(fileext = ".jsonl") 31 | on.exit(unlink(tmp), add = TRUE) 32 | lp <- logger_provider_file$new(list(file_pattern = tmp)) 33 | lgr <- lp$get_logger() 34 | lgr$log("Hello there!") 35 | lp$flush() 36 | 37 | expect_true(file.size(tmp) > 0) 38 | }) 39 | 40 | test_that("otel_logger_provider_file_options_defaults", { 41 | expect_snapshot({ 42 | logger_provider_file$options() 43 | }) 44 | }) 45 | 46 | test_that("otel_logger_provider_flush", {}) 47 | -------------------------------------------------------------------------------- /R/defaults.R: -------------------------------------------------------------------------------- 1 | # Cannot get this from otel because an otel .onLoad might trigger an 2 | # otelsdk load, and the otelsdk .onLoad cannot refer to a half-loaded 3 | # otel in this case. 4 | 5 | span_kinds <- c( 6 | default = "internal", 7 | "server", 8 | "client", 9 | "producer", 10 | "consumer" 11 | ) 12 | 13 | span_status_codes <- c(default = "unset", "ok", "error") 14 | 15 | # TODO: save this once at the beginning of the session, in C++ form 16 | default_resource_attributes <- function() { 17 | # Attributes are not free, so we err on the side of parsimony as to what is 18 | # included by default. 19 | list( 20 | # See: https://opentelemetry.io/docs/specs/semconv/resource/#telemetry-sdk 21 | "telemetry.sdk.language" = "R", 22 | "telemetry.sdk.name" = "opentelemetry", 23 | "telemetry.sdk.version" = as.character(utils::packageVersion("otelsdk")), 24 | # See: https://opentelemetry.io/docs/specs/semconv/resource/process/#process-runtimes 25 | "process.runtime.name" = "R", 26 | "process.runtime.version" = as.character(getRversion()), 27 | "process.runtime.description" = R.version.string, 28 | # See: https://opentelemetry.io/docs/specs/semconv/resource/process/#process 29 | "process.pid" = Sys.getpid(), 30 | "process.owner" = Sys.info()['user'], 31 | # See: https://opentelemetry.io/docs/specs/semconv/resource/os/ 32 | "os.type" = tolower(Sys.info()['sysname']) 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /man/otelsdk-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/otelsdk-package.R 3 | \docType{package} 4 | \name{otelsdk-package} 5 | \alias{otelsdk} 6 | \alias{otelsdk-package} 7 | \title{otelsdk: 'R' 'SDK' and Exporters for 'OpenTelemetry'} 8 | \value{ 9 | Not applicable. 10 | } 11 | \description{ 12 | 'OpenTelemetry' is a collection of tools, 'APIs', and 'SDKs' used to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) for analysis in order to understand your software's performance and behavior. This package contains the 'OpenTelemetry' 'SDK', and exporters. Use this package to export traces, metrics, logs from instrumented 'R' code. Use the 'otel' package to instrument your 'R' code for 'OpenTelemetry'. 13 | } 14 | \examples{ 15 | # Run your R script with OpenTelemetry tracing: 16 | # OTEL_TRACER_EXPORTER=otlp R -f myapp.R 17 | } 18 | \seealso{ 19 | Useful links: 20 | \itemize{ 21 | \item \url{https://otelsdk.r-lib.org} 22 | \item \url{https://github.com/r-lib/otelsdk} 23 | \item Report bugs at \url{https://github.com/r-lib/otelsdk/issues} 24 | } 25 | 26 | } 27 | \author{ 28 | \strong{Maintainer}: Gábor Csárdi \email{csardi.gabor@gmail.com} 29 | 30 | Other contributors: 31 | \itemize{ 32 | \item Posit Software, PBC (\href{https://ror.org/03wc8by49}{ROR}) [copyright holder, funder] 33 | \item opentelemetry-cpp authors [contributor] 34 | } 35 | 36 | } 37 | \keyword{internal} 38 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(format,otel_attributes) 4 | S3method(format,otel_drop_point_data) 5 | S3method(format,otel_histogram_point_data) 6 | S3method(format,otel_instrumentation_scope_data) 7 | S3method(format,otel_last_value_point_data) 8 | S3method(format,otel_metric_data) 9 | S3method(format,otel_metrics_data) 10 | S3method(format,otel_point_data_attributes) 11 | S3method(format,otel_resource_metrics) 12 | S3method(format,otel_scope_metrics) 13 | S3method(format,otel_span_data) 14 | S3method(format,otel_sum_point_data) 15 | S3method(format,otel_trace_flags) 16 | S3method(print,otel_drop_point_data) 17 | S3method(print,otel_histogram_point_data) 18 | S3method(print,otel_instrumentation_scope_data) 19 | S3method(print,otel_last_value_point_data) 20 | S3method(print,otel_metric_data) 21 | S3method(print,otel_metrics_data) 22 | S3method(print,otel_point_data_attributes) 23 | S3method(print,otel_resource_metrics) 24 | S3method(print,otel_scope_metrics) 25 | S3method(print,otel_span_data) 26 | S3method(print,otel_sum_point_data) 27 | export(logger_provider_file) 28 | export(logger_provider_http) 29 | export(logger_provider_stdstream) 30 | export(meter_provider_file) 31 | export(meter_provider_http) 32 | export(meter_provider_memory) 33 | export(meter_provider_stdstream) 34 | export(tracer_provider_file) 35 | export(tracer_provider_http) 36 | export(tracer_provider_memory) 37 | export(tracer_provider_stdstream) 38 | export(with_otel_record) 39 | importFrom(otel,start_span) 40 | useDynLib(otelsdk, .registration = TRUE) 41 | -------------------------------------------------------------------------------- /tests/testthat/test-logger-provider-file.R: -------------------------------------------------------------------------------- 1 | test_that("logger_provider_file", { 2 | tmp <- tempfile(fileext = ".jsonl") 3 | on.exit(unlink(tmp), add = TRUE) 4 | lp <- logger_provider_file_new(list(file_pattern = tmp)) 5 | lgr <- lp$get_logger("org.r-lib.otel") 6 | expect_true(lgr$is_enabled()) 7 | expect_equal(lgr$get_minimum_severity(), otel::log_severity_levels["info"]) 8 | expect_equal(lgr$get_minimum_severity(), c(info = 9L)) 9 | 10 | x <- 1:3 11 | lgr$trace("trace! {x}", attributes = list(a = letters[1:3])) 12 | lgr$debug("debug! {x}", attributes = list(a = letters[1:3])) 13 | lgr$log("log! {x}", severity = "debug", attributes = list(a = letters[1:3])) 14 | lp$flush() 15 | 16 | if (file.exists(tmp)) { 17 | lns <- readLines(tmp) 18 | } else { 19 | lns <- character() 20 | } 21 | expect_equal(length(lns), 0L) 22 | 23 | lgr$warn("warn! {x}", attributes = list(a = letters[1:3])) 24 | lp$flush() 25 | expect_true(file.exists(tmp)) 26 | lns <- readLines(tmp) 27 | obj <- jsonlite::fromJSON(lns[[1]], simplifyVector = FALSE) 28 | expect_equal( 29 | obj$resourceLogs[[1]]$scopeLogs[[1]]$logRecords[[1]]$severityText, 30 | "WARN" 31 | ) 32 | 33 | lgr$error( 34 | "error! {x}", 35 | observed_timestamp = Sys.time(), 36 | attributes = list(a = letters[1:3]) 37 | ) 38 | lp$flush() 39 | lns <- readLines(tmp) 40 | obj <- jsonlite::fromJSON(lns[[2]], simplifyVector = FALSE) 41 | expect_equal( 42 | obj$resourceLogs[[1]]$scopeLogs[[1]]$logRecords[[1]]$severityText, 43 | "ERROR" 44 | ) 45 | }) 46 | -------------------------------------------------------------------------------- /src/cleancall.h: -------------------------------------------------------------------------------- 1 | #ifndef CLEANCALL_H 2 | #define CLEANCALL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | // -------------------------------------------------------------------- 13 | // Internals 14 | // -------------------------------------------------------------------- 15 | 16 | typedef union {void* p; DL_FUNC fn;} fn_ptr; 17 | 18 | #if (defined(R_VERSION) && R_VERSION < R_Version(3, 4, 0)) 19 | SEXP R_MakeExternalPtrFn(DL_FUNC p, SEXP tag, SEXP prot); 20 | DL_FUNC R_ExternalPtrAddrFn(SEXP s); 21 | #endif 22 | 23 | // -------------------------------------------------------------------- 24 | // API for packages that embed cleancall 25 | // -------------------------------------------------------------------- 26 | 27 | // The R API does not have a setter for external function pointers 28 | SEXP cleancall_MakeExternalPtrFn(DL_FUNC p, SEXP tag, SEXP prot); 29 | void cleancall_SetExternalPtrAddrFn(SEXP s, DL_FUNC p); 30 | 31 | #define CLEANCALL_METHOD_RECORD \ 32 | {"cleancall_call", (DL_FUNC) &cleancall_call, 2} 33 | 34 | SEXP cleancall_call(SEXP args, SEXP env); 35 | void cleancall_init(void); 36 | 37 | // -------------------------------------------------------------------- 38 | // Public API 39 | // -------------------------------------------------------------------- 40 | 41 | #define R_CLEANCALL_SUPPORT 1 42 | 43 | SEXP r_with_cleanup_context(SEXP (*fn)(void* data), void* data); 44 | void r_call_on_exit(void (*fn)(void* data), void* data); 45 | void r_call_on_early_exit(void (*fn)(void* data), void* data); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /man/with_otel_record.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/record.R 3 | \name{with_otel_record} 4 | \alias{with_otel_record} 5 | \title{Record OpenTelemetry output, for testing purposes} 6 | \usage{ 7 | with_otel_record( 8 | expr, 9 | what = c("traces", "metrics"), 10 | tracer_opts = list(), 11 | meter_opts = list() 12 | ) 13 | } 14 | \arguments{ 15 | \item{expr}{Expression to evaluate.} 16 | 17 | \item{what}{Character vector, type(s) of OpenTelemetry output to collect.} 18 | 19 | \item{tracer_opts}{Named list of options to pass to the tracer provider.} 20 | 21 | \item{meter_opts}{Named list of options to pass to the meter provider.} 22 | } 23 | \value{ 24 | A list with the output for each output type. Entries: 25 | \itemize{ 26 | \item \code{value}: value of \code{expr}. 27 | \item \code{traces}: the recorded spans, if requested in \code{what}. 28 | \item \code{metrics}: the recorded metrics measurements, if requested in \code{what}. 29 | } 30 | } 31 | \description{ 32 | You can use this function to test that OpenTelemetry output is 33 | correctly generated for your package or application. 34 | } 35 | \details{ 36 | It evaluates the supplied expression, collects OpenTelemetry output 37 | from it and returns it. 38 | 39 | Note: \code{with_otel_record()} cannot record logs yet. 40 | 41 | \code{with_otel_record()} uses \link{tracer_provider_memory} and 42 | \link{meter_provider_memory} internally. 43 | } 44 | \examples{ 45 | spns <- with_otel_record({ 46 | trc <- otel::get_tracer("mytracer") 47 | spn1 <- trc$start_local_active_span() 48 | spn2 <- trc$start_local_active_span("my") 49 | spn2$end() 50 | spn1$end() 51 | NULL 52 | }) 53 | spns 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | Ncpus: 4 34 | 35 | - uses: r-lib/actions/setup-r-dependencies@v2 36 | with: 37 | extra-packages: any::pkgdown, local::. 38 | needs: website 39 | 40 | - name: Build site 41 | run: | 42 | source("tools/scripts/clean-aliases.R") 43 | rmarkdown::render("README.Rmd") 44 | pkgdown::build_site_github_pages(new_process = FALSE, install = TRUE) 45 | shell: Rscript {0} 46 | env: 47 | IN_PKGDOWN: true 48 | 49 | - name: Deploy to GitHub pages 🚀 50 | if: github.event_name != 'pull_request' 51 | uses: JamesIves/github-pages-deploy-action@v4.5.0 52 | with: 53 | clean: false 54 | branch: gh-pages 55 | folder: docs 56 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: otelsdk 2 | Title: R SDK and Exporters for OpenTelemetry 3 | Version: 0.2.2.9000 4 | Authors@R: c( 5 | person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", 6 | role = c("aut", "cre")), 7 | person("Posit Software, PBC", role = c("cph", "fnd"), 8 | comment = c(ROR = "03wc8by49")), 9 | person(family = "opentelemetry-cpp authors", role = "ctb") 10 | ) 11 | Description: OpenTelemetry is a collection of tools, 12 | APIs, and SDKs used to instrument, generate, collect, and export 13 | telemetry data (metrics, logs, and traces) for analysis in order to 14 | understand your software's performance and behavior. 15 | This package contains the OpenTelemetry SDK, and exporters. 16 | Use this package to export traces, metrics, logs from instrumented 17 | R code. Use the otel package to instrument your R code for 18 | OpenTelemetry. 19 | License: MIT + file LICENSE 20 | Encoding: UTF-8 21 | Roxygen: list(markdown = TRUE) 22 | RoxygenNote: 7.3.3 23 | SystemRequirements: cmake, protobuf-compiler, libprotobuf, libcurl, zlib, 24 | GNU make 25 | Imports: 26 | otel, 27 | utils 28 | Suggests: 29 | callr, 30 | cli, 31 | curl, 32 | gh, 33 | jsonlite, 34 | processx, 35 | ps, 36 | rlang, 37 | spelling, 38 | testthat (>= 3.0.0), 39 | webfakes, 40 | withr 41 | Config/testthat/edition: 3 42 | Config/testthat/parallel: TRUE 43 | URL: https://otelsdk.r-lib.org, https://github.com/r-lib/otelsdk 44 | Config/Needs/check: r-lib/otel, gaborcsardi/testthatlabs 45 | Config/Needs/coverage: r-lib/otel, gaborcsardi/testthatlabs 46 | Config/Needs/website: tidyverse/tidytemplate, r-lib/otel 47 | BugReports: https://github.com/r-lib/otelsdk/issues 48 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/logger-provider-http.md: -------------------------------------------------------------------------------- 1 | # logger_provider_http_options 2 | 3 | Code 4 | logger_provider_http_options() 5 | Output 6 | $url 7 | [1] "http://localhost:4318/v1/logs" 8 | 9 | $content_type 10 | http/protobuf 11 | 1 12 | 13 | $json_bytes_mapping 14 | [1] 0 15 | 16 | $use_json_name 17 | [1] FALSE 18 | 19 | $console_debug 20 | [1] FALSE 21 | 22 | $timeout 23 | [1] 10 24 | 25 | $http_headers 26 | named character(0) 27 | 28 | $ssl_insecure_skip_verify 29 | [1] FALSE 30 | 31 | $ssl_ca_cert_path 32 | [1] "" 33 | 34 | $ssl_ca_cert_string 35 | [1] "" 36 | 37 | $ssl_client_key_path 38 | [1] "" 39 | 40 | $ssl_client_key_string 41 | [1] "" 42 | 43 | $ssl_client_cert_path 44 | [1] "" 45 | 46 | $ssl_client_cert_string 47 | [1] "" 48 | 49 | $ssl_min_tls 50 | [1] "" 51 | 52 | $ssl_max_tls 53 | [1] "" 54 | 55 | $ssl_cipher 56 | [1] "" 57 | 58 | $ssl_cipher_suite 59 | [1] "" 60 | 61 | $compression 62 | [1] "none" 63 | 64 | $retry_policy_max_attempts 65 | [1] 5 66 | 67 | $retry_policy_initial_backoff 68 | [1] 1000 69 | 70 | $retry_policy_max_backoff 71 | [1] 5000 72 | 73 | $retry_policy_backoff_multiplier 74 | [1] 1.5 75 | 76 | $max_queue_size 77 | [1] 2048 78 | 79 | $max_export_batch_size 80 | [1] 512 81 | 82 | $schedule_delay 83 | [1] 5000 84 | 85 | 86 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/tracer-provider-http.md: -------------------------------------------------------------------------------- 1 | # HTTP tracer provider defaults 2 | 3 | Code 4 | tracer_provider_http_options() 5 | Output 6 | $url 7 | [1] "http://localhost:4318/v1/traces" 8 | 9 | $content_type 10 | http/protobuf 11 | 1 12 | 13 | $json_bytes_mapping 14 | [1] 0 15 | 16 | $use_json_name 17 | [1] FALSE 18 | 19 | $console_debug 20 | [1] FALSE 21 | 22 | $timeout 23 | [1] 10 24 | 25 | $http_headers 26 | named character(0) 27 | 28 | $ssl_insecure_skip_verify 29 | [1] FALSE 30 | 31 | $ssl_ca_cert_path 32 | [1] "" 33 | 34 | $ssl_ca_cert_string 35 | [1] "" 36 | 37 | $ssl_client_key_path 38 | [1] "" 39 | 40 | $ssl_client_key_string 41 | [1] "" 42 | 43 | $ssl_client_cert_path 44 | [1] "" 45 | 46 | $ssl_client_cert_string 47 | [1] "" 48 | 49 | $ssl_min_tls 50 | [1] "" 51 | 52 | $ssl_max_tls 53 | [1] "" 54 | 55 | $ssl_cipher 56 | [1] "" 57 | 58 | $ssl_cipher_suite 59 | [1] "" 60 | 61 | $compression 62 | [1] "none" 63 | 64 | $retry_policy_max_attempts 65 | [1] 5 66 | 67 | $retry_policy_initial_backoff 68 | [1] 1000 69 | 70 | $retry_policy_max_backoff 71 | [1] 5000 72 | 73 | $retry_policy_backoff_multiplier 74 | [1] 1.5 75 | 76 | $max_queue_size 77 | [1] 2048 78 | 79 | $max_export_batch_size 80 | [1] 512 81 | 82 | $schedule_delay 83 | [1] 5000 84 | 85 | 86 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/tracer-provider-http-default.md: -------------------------------------------------------------------------------- 1 | # HTTP tracer provider defaults 2 | 3 | Code 4 | tracer_provider_http$options() 5 | Output 6 | $url 7 | [1] "http://localhost:4318/v1/traces" 8 | 9 | $content_type 10 | http/protobuf 11 | 1 12 | 13 | $json_bytes_mapping 14 | [1] 0 15 | 16 | $use_json_name 17 | [1] FALSE 18 | 19 | $console_debug 20 | [1] FALSE 21 | 22 | $timeout 23 | [1] 10 24 | 25 | $http_headers 26 | named character(0) 27 | 28 | $ssl_insecure_skip_verify 29 | [1] FALSE 30 | 31 | $ssl_ca_cert_path 32 | [1] "" 33 | 34 | $ssl_ca_cert_string 35 | [1] "" 36 | 37 | $ssl_client_key_path 38 | [1] "" 39 | 40 | $ssl_client_key_string 41 | [1] "" 42 | 43 | $ssl_client_cert_path 44 | [1] "" 45 | 46 | $ssl_client_cert_string 47 | [1] "" 48 | 49 | $ssl_min_tls 50 | [1] "" 51 | 52 | $ssl_max_tls 53 | [1] "" 54 | 55 | $ssl_cipher 56 | [1] "" 57 | 58 | $ssl_cipher_suite 59 | [1] "" 60 | 61 | $compression 62 | [1] "none" 63 | 64 | $retry_policy_max_attempts 65 | [1] 5 66 | 67 | $retry_policy_initial_backoff 68 | [1] 1000 69 | 70 | $retry_policy_max_backoff 71 | [1] 5000 72 | 73 | $retry_policy_backoff_multiplier 74 | [1] 1.5 75 | 76 | $max_queue_size 77 | [1] 2048 78 | 79 | $max_export_batch_size 80 | [1] 512 81 | 82 | $schedule_delay 83 | [1] 5000 84 | 85 | 86 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/meter-provider-http.md: -------------------------------------------------------------------------------- 1 | # meter_provider_http_options 2 | 3 | Code 4 | meter_provider_http_options() 5 | Output 6 | $url 7 | [1] "http://localhost:4318/v1/metrics" 8 | 9 | $content_type 10 | http/protobuf 11 | 1 12 | 13 | $json_bytes_mapping 14 | [1] 0 15 | 16 | $use_json_name 17 | [1] FALSE 18 | 19 | $console_debug 20 | [1] FALSE 21 | 22 | $timeout 23 | [1] 10 24 | 25 | $http_headers 26 | named character(0) 27 | 28 | $ssl_insecure_skip_verify 29 | [1] FALSE 30 | 31 | $ssl_ca_cert_path 32 | [1] "" 33 | 34 | $ssl_ca_cert_string 35 | [1] "" 36 | 37 | $ssl_client_key_path 38 | [1] "" 39 | 40 | $ssl_client_key_string 41 | [1] "" 42 | 43 | $ssl_client_cert_path 44 | [1] "" 45 | 46 | $ssl_client_cert_string 47 | [1] "" 48 | 49 | $ssl_min_tls 50 | [1] "" 51 | 52 | $ssl_max_tls 53 | [1] "" 54 | 55 | $ssl_cipher 56 | [1] "" 57 | 58 | $ssl_cipher_suite 59 | [1] "" 60 | 61 | $compression 62 | [1] "none" 63 | 64 | $retry_policy_max_attempts 65 | [1] 5 66 | 67 | $retry_policy_initial_backoff 68 | [1] 1000 69 | 70 | $retry_policy_max_backoff 71 | [1] 5000 72 | 73 | $retry_policy_backoff_multiplier 74 | [1] 1.5 75 | 76 | $export_interval 77 | [1] 60000 78 | 79 | $export_timeout 80 | [1] 30000 81 | 82 | $aggregation_temporality 83 | cumulative 84 | 2 85 | 86 | 87 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://otelsdk.r-lib.org 2 | 3 | template: 4 | package: tidytemplate 5 | bootstrap: 5 6 | 7 | includes: 8 | in_header: | 9 | 10 | 11 | 12 | development: 13 | mode: auto 14 | 15 | navbar: 16 | structure: 17 | left: [gettingstarted, reference] 18 | right: [news] 19 | components: 20 | gettingstarted: 21 | text: Getting Started 22 | menu: 23 | - text: Instrumenting R Packages (otel package) 24 | href: https://otel.r-lib.org/reference/gettingstarted.html 25 | - text: Collecting Telemetry Data 26 | href: reference/collecting.html 27 | 28 | reference: 29 | - title: Other Documentation 30 | desc: | 31 | This is the reference manual of the otelsdk package. Other forms of 32 | documentation: 33 | 34 | * [Getting Started](https://otel.r-lib.org/reference/gettingstarted.html), 35 | a tutorial and cookbook for instrumentation. 36 | * [Collecting telemetry data](../reference/collecting.html), a tutorial 37 | and cookbook on telemetry data collection. 38 | 39 | - title: Configuration 40 | - contents: 41 | - "Environment Variables" 42 | - "Time Interval Options" 43 | - "File Size Options" 44 | 45 | - title: Traces 46 | - contents: 47 | - starts_with("tracer_provider_") 48 | 49 | - title: Logs 50 | - contents: 51 | - starts_with("logger_provider_") 52 | 53 | - title: Metrics 54 | - contents: 55 | - starts_with("meter_provider_") 56 | 57 | - title: Utility functions 58 | - contents: 59 | - with_otel_record 60 | 61 | - title: internal 62 | contents: 63 | - collecting 64 | -------------------------------------------------------------------------------- /man/tracer_provider_memory.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tracer-provider-memory.R 3 | \docType{data} 4 | \name{tracer_provider_memory} 5 | \alias{tracer_provider_memory} 6 | \title{In-memory tracer provider for testing} 7 | \value{ 8 | \code{tracer_provider_memory$new()} returns an \link[otel:otel_tracer_provider]{otel::otel_tracer_provider} 9 | object. \code{tp$get_spans()} returns a named list of recorded spans, with 10 | the span names as names. 11 | 12 | \code{tracer_provider_memory$options()} returns a named list, the current 13 | values for all options. 14 | } 15 | \description{ 16 | Collects spans in memory. This is useful for testing your instrumented 17 | R package or application. 18 | 19 | \code{\link[=with_otel_record]{with_otel_record()}} uses this tracer provider. 20 | Use \code{\link[=with_otel_record]{with_otel_record()}} in your tests to record telemetry and check 21 | that it is correct. 22 | } 23 | \section{Usage}{ 24 | \if{html}{\out{
}}\preformatted{tp <- tracer_provider_memory$new(opts = NULL) 25 | tp$get_spans() 26 | tracer_provider_memory$options() 27 | }\if{html}{\out{
}} 28 | 29 | \code{tp$get_spans()} erases the internal buffer of the tracer provider. 30 | } 31 | 32 | \section{Arguments}{ 33 | \itemize{ 34 | \item \code{opts}: Named list of options. See below. 35 | } 36 | } 37 | 38 | \section{Options}{ 39 | \subsection{Memory exporter options}{ 40 | \itemize{ 41 | \item \code{buffer_size}: buffer size, this is the maximum number of spans or 42 | metrics measurements that the provider can record. 43 | Must be positive. Value is set from 44 | \itemize{ 45 | \item the \code{opts} argument, or 46 | \item the \code{OTEL_R_EXPORTER_MEMORY_TRACES_BUFFER_SIZE} environment variable, or 47 | \item the \code{OTEL_R_EXPORTER_MEMORY_BUFFER_SIZE} environment variable, or 48 | \item the default is \code{100}. 49 | } 50 | } 51 | } 52 | } 53 | 54 | \examples{ 55 | tracer_provider_memory$options() 56 | } 57 | \keyword{datasets} 58 | -------------------------------------------------------------------------------- /tests/testthat/test-logger-provider-stdout.R: -------------------------------------------------------------------------------- 1 | test_that("logger_provider_stdstream", { 2 | logger_provider <- logger_provider_stdstream_new() 3 | logger <- logger_provider$get_logger("mylogger") 4 | expect_true(logger$is_enabled()) 5 | expect_equal(logger$get_minimum_severity(), otel::log_severity_levels["info"]) 6 | }) 7 | 8 | test_that("log to file", { 9 | tmp <- tempfile() 10 | on.exit(unlink(tmp), add = TRUE) 11 | lp <- logger_provider_stdstream_new(list(output = tmp)) 12 | lgr <- lp$get_logger("mylogger") 13 | 14 | expect_true(lgr$is_enabled()) 15 | expect_equal(lgr$get_minimum_severity(), otel::log_severity_levels["info"]) 16 | lgr$log("This is a simple log message") 17 | lgr$log("This is a warning", severity = "warn") 18 | 19 | ts <- Sys.time() 20 | lgr$log("This is a log message.") 21 | lgr$log( 22 | "This is a log message with attributes", 23 | attributes = list(foo = "bar"), 24 | timestamp = ts2 <- Sys.time() 25 | ) 26 | lp$flush() 27 | 28 | expect_true(file.exists(tmp)) 29 | spns <- parse_spans(tmp) 30 | test_fields <- c( 31 | "severity_num", 32 | "severity_text", 33 | "body", 34 | "attributes", 35 | "trace_id", 36 | "span_id" 37 | ) 38 | expect_snapshot({ 39 | spns[[1]][test_fields] 40 | spns[[2]][test_fields] 41 | spns[[3]][test_fields] 42 | spns[[4]][test_fields] 43 | }) 44 | 45 | # time stamps are set 46 | expect_match(spns[[3]]$timestamp, "^[0-9]{10}[0-9]*$") 47 | expect_match(spns[[3]]$observed_timestamp, "^[0-9]{10}[0-9]*$") 48 | expect_true( 49 | as.integer(substr(spns[[3]]$timestamp, 1, 10)) - as.integer(ts) < 10 50 | ) 51 | expect_true( 52 | as.integer(substr(spns[[3]]$observed_timestamp, 1, 10)) - as.integer(ts) < 53 | 10 54 | ) 55 | expect_equal( 56 | substr(spns[[4]]$timestamp, 1, 10), 57 | as.character(as.integer(ts2)) 58 | ) 59 | }) 60 | 61 | test_that("logger_provider_stdstream_options", { 62 | expect_snapshot(logger_provider_stdstream_options()) 63 | }) 64 | -------------------------------------------------------------------------------- /man/logger_provider_stdstream.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logger-provider-stdout.R 3 | \docType{data} 4 | \name{logger_provider_stdstream} 5 | \alias{logger_provider_stdstream} 6 | \title{Logger provider to write to the standard output or standard error or 7 | to a file} 8 | \value{ 9 | \code{logger_provider_stdstream$new()} returns an \link[otel:otel_logger_provider]{otel::otel_logger_provider} 10 | object. 11 | 12 | \code{logger_provider_stdstream$options()} returns a named list, the current 13 | values of the options. 14 | } 15 | \description{ 16 | Writes logs to the standard output or error, or to a file. Useful for 17 | debugging. 18 | } 19 | \section{Usage}{ 20 | Externally: 21 | 22 | \if{html}{\out{
}}\preformatted{OTEL_LOGS_EXPORTER=console 23 | OTEL_LOGS_EXPORTER=stderr 24 | }\if{html}{\out{
}} 25 | 26 | From R: 27 | 28 | \if{html}{\out{
}}\preformatted{logger_provider_stdstream$new(opts = NULL) 29 | logger_provider_stdstream$options() 30 | }\if{html}{\out{
}} 31 | } 32 | 33 | \section{Arguments}{ 34 | \code{opts}: Named list of options. See below. 35 | } 36 | 37 | \section{Options}{ 38 | \subsection{Standard stream exporter options}{ 39 | \itemize{ 40 | \item \code{output}: where to write the output. Can be 41 | \itemize{ 42 | \item \code{"stdout"}: write output to the standard output, 43 | \item \code{"stderr"}: write output to the standard error, 44 | \item another string: write output to a file. (To write output to a file 45 | named \code{"stdout"} or \code{"stderr"}, use a \verb{./} prefix.) 46 | } 47 | 48 | Value is set from 49 | \itemize{ 50 | \item the \code{opts} argument, or 51 | \item the \code{OTEL_R_EXPORTER_STDSTREAM_LOGS_OUTPUT} environment variable, or 52 | \item the \code{OTEL_R_EXPORTER_STDSTREAM_OUTPUT} environment variable, or 53 | \item the default is \code{"stdout"}. 54 | } 55 | } 56 | } 57 | } 58 | 59 | \examples{ 60 | logger_provider_stdstream$options() 61 | } 62 | \keyword{datasets} 63 | -------------------------------------------------------------------------------- /man/tracer_provider_stdstream.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/tracer-provider-stdout.R 3 | \docType{data} 4 | \name{tracer_provider_stdstream} 5 | \alias{tracer_provider_stdstream} 6 | \title{Tracer provider to write to the standard output or standard error or 7 | to a file} 8 | \value{ 9 | \code{tracer_provider_stdstream$new()} returns an \link[otel:otel_tracer_provider]{otel::otel_tracer_provider} 10 | object. 11 | 12 | \code{tracer_provider_stdstream$options()} returns a named list, the current 13 | values of the options. 14 | } 15 | \description{ 16 | Writes spans to the standard output or error, or to a file. Useful for 17 | debugging. 18 | } 19 | \section{Usage}{ 20 | Externally: 21 | 22 | \if{html}{\out{
}}\preformatted{OTEL_TRACES_EXPORTER=console 23 | OTEL_TRACES_EXPORTER=stderr 24 | }\if{html}{\out{
}} 25 | 26 | From R: 27 | 28 | \if{html}{\out{
}}\preformatted{tracer_provider_stdstream$new(opts = NULL) 29 | tracer_provider_stdstream$options() 30 | }\if{html}{\out{
}} 31 | } 32 | 33 | \section{Arguments}{ 34 | \code{opts}: Named list of options. See below. 35 | } 36 | 37 | \section{Options}{ 38 | \subsection{Standard stream exporter options}{ 39 | \itemize{ 40 | \item \code{output}: where to write the output. Can be 41 | \itemize{ 42 | \item \code{"stdout"}: write output to the standard output, 43 | \item \code{"stderr"}: write output to the standard error, 44 | \item another string: write output to a file. (To write output to a file 45 | named \code{"stdout"} or \code{"stderr"}, use a \verb{./} prefix.) 46 | } 47 | 48 | Value is set from 49 | \itemize{ 50 | \item the \code{opts} argument, or 51 | \item the \code{OTEL_R_EXPORTER_STDSTREAM_TRACES_OUTPUT} environment variable, or 52 | \item the \code{OTEL_R_EXPORTER_STDSTREAM_OUTPUT} environment variable, or 53 | \item the default is \code{"stdout"}. 54 | } 55 | } 56 | } 57 | } 58 | 59 | \examples{ 60 | tracer_provider_stdstream$options() 61 | } 62 | \keyword{datasets} 63 | -------------------------------------------------------------------------------- /src/r-collector.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "Rinternals.h" 4 | 5 | #include "otel_common.h" 6 | #include "otel_common_r.h" 7 | #include "errors.h" 8 | 9 | SEXP otel_parse_log_record(SEXP str) { 10 | const char *str_ = (const char*) RAW(str); 11 | size_t len = Rf_length(str); 12 | struct otel_collector_resource_logs rl = { 0 }; 13 | if (otel_decode_log_record_(str_, len, &rl)) { 14 | R_THROW_ERROR("Failed to parse Protobuf log message"); 15 | } 16 | SEXP res = c2r_otel_collector_resource_logs(&rl); 17 | // TODO: cleancall 18 | otel_collector_resource_logs_free(&rl); 19 | return res; 20 | } 21 | 22 | SEXP otel_parse_metrics_record(SEXP str) { 23 | const char *str_ = (const char*) RAW(str); 24 | size_t len = Rf_length(str); 25 | struct otel_collector_resource_metrics rm = { 0 }; 26 | if (otel_decode_metrics_record_(str_, len, &rm)) { 27 | R_THROW_ERROR("Failed to parse Protobuf metrics message"); 28 | } 29 | SEXP res = c2r_otel_collector_resource_metrics(&rm); 30 | // TODO: cleancall 31 | otel_collector_resource_metrics_free(&rm); 32 | return res; 33 | } 34 | 35 | SEXP otel_encode_response( 36 | SEXP signal_, SEXP result_, SEXP errmsg_, SEXP rejected_, 37 | SEXP error_code_) { 38 | const int signal = INTEGER(signal_)[0]; 39 | const int result = INTEGER(result_)[0]; 40 | const char *errmsg = Rf_isNull(errmsg_) ? 0 : CHAR(STRING_ELT(errmsg_, 0)); 41 | const int rejected = INTEGER(rejected_)[0]; 42 | const int error_code = INTEGER(error_code_)[0]; 43 | struct otel_string msg = { 0 }; 44 | if (otel_encode_response_( 45 | signal, result, errmsg, rejected, error_code, &msg)) { 46 | // # nocov start LCOV_EXCL_START 47 | R_THROW_ERROR("Failed to encode Protobuf response"); 48 | }; 49 | // # nocov end LCOV_EXCL_STOP 50 | SEXP res = Rf_protect(Rf_allocVector(RAWSXP, msg.size)); 51 | if (msg.size > 0) { 52 | memcpy(RAW(res), msg.s, msg.size); 53 | } 54 | // TODO: cleancall 55 | otel_string_free(&msg); 56 | 57 | Rf_unprotect(1); 58 | return res; 59 | } 60 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/logger-provider-stdout.md: -------------------------------------------------------------------------------- 1 | # log to file 2 | 3 | Code 4 | spns[[1]][test_fields] 5 | Output 6 | $severity_num 7 | [1] "9" 8 | 9 | $severity_text 10 | [1] "INFO" 11 | 12 | $body 13 | [1] "This is a simple log message" 14 | 15 | $attributes 16 | named list() 17 | 18 | $trace_id 19 | [1] "00000000000000000000000000000000" 20 | 21 | $span_id 22 | [1] "0000000000000000" 23 | 24 | Code 25 | spns[[2]][test_fields] 26 | Output 27 | $severity_num 28 | [1] "13" 29 | 30 | $severity_text 31 | [1] "WARN" 32 | 33 | $body 34 | [1] "This is a warning" 35 | 36 | $attributes 37 | named list() 38 | 39 | $trace_id 40 | [1] "00000000000000000000000000000000" 41 | 42 | $span_id 43 | [1] "0000000000000000" 44 | 45 | Code 46 | spns[[3]][test_fields] 47 | Output 48 | $severity_num 49 | [1] "9" 50 | 51 | $severity_text 52 | [1] "INFO" 53 | 54 | $body 55 | [1] "This is a log message." 56 | 57 | $attributes 58 | named list() 59 | 60 | $trace_id 61 | [1] "00000000000000000000000000000000" 62 | 63 | $span_id 64 | [1] "0000000000000000" 65 | 66 | Code 67 | spns[[4]][test_fields] 68 | Output 69 | $severity_num 70 | [1] "9" 71 | 72 | $severity_text 73 | [1] "INFO" 74 | 75 | $body 76 | [1] "This is a log message with attributes" 77 | 78 | $attributes 79 | $attributes$foo 80 | [1] "bar" 81 | 82 | 83 | $trace_id 84 | [1] "00000000000000000000000000000000" 85 | 86 | $span_id 87 | [1] "0000000000000000" 88 | 89 | 90 | # logger_provider_stdstream_options 91 | 92 | Code 93 | logger_provider_stdstream_options() 94 | Output 95 | $output 96 | [1] "stdout" 97 | 98 | 99 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | Ncpus: 4 25 | 26 | - uses: r-lib/actions/setup-r-dependencies@v2 27 | with: 28 | extra-packages: any::covr, any::xml2 29 | needs: coverage 30 | 31 | - name: Test coverage 32 | run: | 33 | cov <- covr::package_coverage( 34 | quiet = FALSE, 35 | clean = FALSE, 36 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 37 | ) 38 | print(cov) 39 | covr::to_cobertura(cov) 40 | shell: Rscript {0} 41 | 42 | - uses: codecov/codecov-action@v5 43 | with: 44 | # Fail if error if not on PR, or if on PR and token is given 45 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 46 | files: ./cobertura.xml 47 | plugins: noop 48 | disable_search: true 49 | token: ${{ secrets.CODECOV_TOKEN }} 50 | 51 | - name: Show testthat output 52 | if: always() 53 | run: | 54 | ## -------------------------------------------------------------------- 55 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 56 | shell: bash 57 | 58 | - name: Upload test results 59 | if: failure() 60 | uses: actions/upload-artifact@v4 61 | with: 62 | name: coverage-test-failures 63 | path: ${{ runner.temp }}/package 64 | -------------------------------------------------------------------------------- /src/status.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.rpc; 18 | 19 | import "google/protobuf/any.proto"; 20 | 21 | option cc_enable_arenas = true; 22 | option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "StatusProto"; 25 | option java_package = "com.google.rpc"; 26 | option objc_class_prefix = "RPC"; 27 | 28 | // The `Status` type defines a logical error model that is suitable for 29 | // different programming environments, including REST APIs and RPC APIs. It is 30 | // used by [gRPC](https://github.com/grpc). Each `Status` message contains 31 | // three pieces of data: error code, error message, and error details. 32 | // 33 | // You can find out more about this error model and how to work with it in the 34 | // [API Design Guide](https://cloud.google.com/apis/design/errors). 35 | message Status { 36 | // The status code, which should be an enum value of 37 | // [google.rpc.Code][google.rpc.Code]. 38 | int32 code = 1; 39 | 40 | // A developer-facing error message, which should be in English. Any 41 | // user-facing error message should be localized and sent in the 42 | // [google.rpc.Status.details][google.rpc.Status.details] field, or localized 43 | // by the client. 44 | string message = 2; 45 | 46 | // A list of messages that carry the error details. There is a common set of 47 | // message types for APIs to use. 48 | repeated google.protobuf.Any details = 3; 49 | } 50 | -------------------------------------------------------------------------------- /R/tracer-provider-stdout.R: -------------------------------------------------------------------------------- 1 | tracer_provider_stdstream_new <- function(opts = NULL) { 2 | opts <- as_tracer_provider_stdstream_options(opts) 3 | self <- new_object( 4 | c("otel_tracer_provider_stdstream", "otel_tracer_provider"), 5 | get_tracer = function( 6 | name = NULL, 7 | version = NULL, 8 | schema_url = NULL, 9 | attributes = NULL, 10 | ... 11 | ) { 12 | tracer_new(self, name, version, schema_url, attributes, ...) 13 | }, 14 | flush = function() { 15 | ccall(otel_tracer_provider_flush, self$xptr) 16 | } 17 | ) 18 | attributes <- as_otel_attributes(the$default_resource_attributes) 19 | self$xptr <- ccall(otel_create_tracer_provider_stdstream, opts, attributes) 20 | self 21 | } 22 | 23 | tracer_provider_stdstream_options <- function() { 24 | as_tracer_provider_stdstream_options(NULL) 25 | } 26 | 27 | #' Tracer provider to write to the standard output or standard error or 28 | #' to a file 29 | #' 30 | #' @description 31 | #' Writes spans to the standard output or error, or to a file. Useful for 32 | #' debugging. 33 | #' 34 | #' # Usage 35 | #' 36 | #' Externally: 37 | #' ``` 38 | #' OTEL_TRACES_EXPORTER=console 39 | #' OTEL_TRACES_EXPORTER=stderr 40 | #' ``` 41 | #' 42 | #' From R: 43 | #' ``` 44 | #' tracer_provider_stdstream$new(opts = NULL) 45 | #' tracer_provider_stdstream$options() 46 | #' ``` 47 | #' 48 | #' # Arguments 49 | #' 50 | #' `opts`: Named list of options. See below. 51 | #' 52 | #' # Options 53 | #' 54 | #' ## Standard stream exporter options 55 | #' 56 | #' ```{r} 57 | #' #| echo: FALSE 58 | #' #| results: asis 59 | #' cat(doc_stdstream_exporter_options( 60 | #' tracer_provider_stdstream_options_evs() 61 | #' )) 62 | #' ``` 63 | #' 64 | #' @return 65 | #' `tracer_provider_stdstream$new()` returns an [otel::otel_tracer_provider] 66 | #' object. 67 | #' 68 | #' `tracer_provider_stdstream$options()` returns a named list, the current 69 | #' values of the options. 70 | #' 71 | #' @format NULL 72 | #' @usage NULL 73 | #' @export 74 | #' @examples 75 | #' tracer_provider_stdstream$options() 76 | 77 | tracer_provider_stdstream <- list( 78 | new = tracer_provider_stdstream_new, 79 | options = tracer_provider_stdstream_options 80 | ) 81 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | `%||%` <- function(l, r) if (is.null(l)) r else l 2 | 3 | is_true <- function(x) { 4 | is.logical(x) && length(x) == 1L && !is.na(x) && x 5 | } 6 | 7 | is_false <- function(x) { 8 | is.logical(x) && length(x) == 1L && !is.na(x) && !x 9 | } 10 | 11 | defer <- function(expr, envir = parent.frame()) { 12 | finalizer <- as.call(list(function() expr)) 13 | do.call(base::on.exit, list(finalizer, TRUE, FALSE), envir = envir) 14 | } 15 | 16 | map_chr <- function(X, FUN, ...) { 17 | vapply(X, FUN, FUN.VALUE = character(1), ...) 18 | } 19 | 20 | map_lgl <- function(X, FUN, ...) { 21 | vapply(X, FUN, FUN.VALUE = logical(1), ...) 22 | } 23 | 24 | map_int <- function(X, FUN, ...) { 25 | vapply(X, FUN, FUN.VALUE = integer(1), ...) 26 | } 27 | 28 | get_env <- function(n) { 29 | v <- Sys.getenv(n) 30 | if (v != "") v else NULL 31 | } 32 | 33 | get_current_error <- function() { 34 | fail <- NULL 35 | err <- tryCatch( 36 | suppressWarnings(ccall(otel_error_object)), 37 | error = function(e) { 38 | fail <<- e 39 | NULL 40 | } 41 | ) 42 | 43 | if (!is.null(fail)) { 44 | # tried, but failed 45 | m <- paste("Could not get the error message.", conditionMessage(fail)) 46 | list(tried = TRUE, success = FALSE, object = NULL, error = m) 47 | } else if (!err[[1]]) { 48 | # didn't (couldn't) try 49 | m <- paste( 50 | "This version of otelsdk cannot get error messages.", 51 | "Make sure that you are using the latest version." 52 | ) 53 | list(tried = FALSE, success = NA, object = NULL, error = m) 54 | } else if (is.null(err[[2]])) { 55 | # tried, but did not find any errors. 56 | m <- paste( 57 | "Cannot find error message, this is possibly a bug in the otelsdk", 58 | "package. Make sure that you are using the latest version." 59 | ) 60 | list(tried = TRUE, success = FALSE, object = NULL, error = m) 61 | } else { 62 | # all good 63 | list(tried = TRUE, success = TRUE, object = err[[2]], error = NULL) 64 | } 65 | } 66 | 67 | plural <- function(x) { 68 | if (x == 0 || x > 1) "s" else "" 69 | } 70 | 71 | find_instrumentation_scope <- function(name = NULL) { 72 | otel::default_tracer_name(name) 73 | } 74 | 75 | empty_atomic_as_null <- function(x) { 76 | if (is.atomic(x) && length(x) == 0) { 77 | NULL 78 | } else { 79 | x 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/logger-sdk-cc.md: -------------------------------------------------------------------------------- 1 | # otel_logger_provider_file_options_defaults_ 2 | 3 | Code 4 | logger_provider_file$options() 5 | Output 6 | $file_pattern 7 | [1] "logs-%N.jsonl" 8 | 9 | $alias_pattern 10 | [1] "logs-latest.jsonl" 11 | 12 | $flush_interval 13 | [1] 30 14 | 15 | $flush_count 16 | [1] 256 17 | 18 | $file_size 19 | [1] 20971520 20 | 21 | $rotate_size 22 | [1] 10 23 | 24 | 25 | # otel_logger_provider_http_default_options_ 26 | 27 | Code 28 | logger_provider_http$options() 29 | Output 30 | $url 31 | [1] "http://localhost:4318/v1/logs" 32 | 33 | $content_type 34 | http/protobuf 35 | 1 36 | 37 | $json_bytes_mapping 38 | [1] 0 39 | 40 | $use_json_name 41 | [1] FALSE 42 | 43 | $console_debug 44 | [1] FALSE 45 | 46 | $timeout 47 | [1] 10 48 | 49 | $http_headers 50 | named character(0) 51 | 52 | $ssl_insecure_skip_verify 53 | [1] FALSE 54 | 55 | $ssl_ca_cert_path 56 | [1] "" 57 | 58 | $ssl_ca_cert_string 59 | [1] "" 60 | 61 | $ssl_client_key_path 62 | [1] "" 63 | 64 | $ssl_client_key_string 65 | [1] "" 66 | 67 | $ssl_client_cert_path 68 | [1] "" 69 | 70 | $ssl_client_cert_string 71 | [1] "" 72 | 73 | $ssl_min_tls 74 | [1] "" 75 | 76 | $ssl_max_tls 77 | [1] "" 78 | 79 | $ssl_cipher 80 | [1] "" 81 | 82 | $ssl_cipher_suite 83 | [1] "" 84 | 85 | $compression 86 | [1] "none" 87 | 88 | $retry_policy_max_attempts 89 | [1] 5 90 | 91 | $retry_policy_initial_backoff 92 | [1] 1000 93 | 94 | $retry_policy_max_backoff 95 | [1] 5000 96 | 97 | $retry_policy_backoff_multiplier 98 | [1] 1.5 99 | 100 | $max_queue_size 101 | [1] 2048 102 | 103 | $max_export_batch_size 104 | [1] 512 105 | 106 | $schedule_delay 107 | [1] 5000 108 | 109 | 110 | -------------------------------------------------------------------------------- /R/logger-provider-stdout.R: -------------------------------------------------------------------------------- 1 | logger_provider_stdstream_new <- function(opts = NULL) { 2 | opts <- as_logger_provider_stdstream_options(opts) 3 | 4 | self <- new_object( 5 | c("otel_logger_provider_stdstream", "otel_logger_provider"), 6 | get_logger = function( 7 | name = NULL, 8 | minimum_severity = NULL, 9 | version = NULL, 10 | schema_url = NULL, 11 | attributes = NULL, 12 | ... 13 | ) { 14 | logger_new( 15 | self, 16 | name, 17 | minimum_severity, 18 | version, 19 | schema_url, 20 | attributes, 21 | ... 22 | ) 23 | }, 24 | flush = function() { 25 | ccall(otel_logger_provider_flush, self$xptr) 26 | } 27 | ) 28 | 29 | attributes <- as_otel_attributes(the$default_resource_attributes) 30 | self$xptr <- ccall(otel_create_logger_provider_stdstream, opts, attributes) 31 | self 32 | } 33 | 34 | logger_provider_stdstream_options <- function() { 35 | as_logger_provider_stdstream_options(NULL) 36 | } 37 | 38 | #' Logger provider to write to the standard output or standard error or 39 | #' to a file 40 | #' 41 | #' @description 42 | #' Writes logs to the standard output or error, or to a file. Useful for 43 | #' debugging. 44 | #' 45 | #' # Usage 46 | #' 47 | #' Externally: 48 | #' ``` 49 | #' OTEL_LOGS_EXPORTER=console 50 | #' OTEL_LOGS_EXPORTER=stderr 51 | #' ``` 52 | #' 53 | #' From R: 54 | #' ``` 55 | #' logger_provider_stdstream$new(opts = NULL) 56 | #' logger_provider_stdstream$options() 57 | #' ``` 58 | #' 59 | #' # Arguments 60 | #' 61 | #' `opts`: Named list of options. See below. 62 | #' 63 | #' # Options 64 | #' 65 | #' ## Standard stream exporter options 66 | #' 67 | #' ```{r} 68 | #' #| echo: FALSE 69 | #' #| results: asis 70 | #' cat(doc_stdstream_exporter_options( 71 | #' logger_provider_stdstream_options_evs() 72 | #' )) 73 | #' ``` 74 | #' 75 | #' @return 76 | #' `logger_provider_stdstream$new()` returns an [otel::otel_logger_provider] 77 | #' object. 78 | #' 79 | #' `logger_provider_stdstream$options()` returns a named list, the current 80 | #' values of the options. 81 | #' 82 | #' @format NULL 83 | #' @usage NULL 84 | #' @export 85 | #' @examples 86 | #' logger_provider_stdstream$options() 87 | 88 | logger_provider_stdstream <- list( 89 | new = logger_provider_stdstream_new, 90 | options = logger_provider_stdstream_options 91 | ) 92 | -------------------------------------------------------------------------------- /R/tracer.R: -------------------------------------------------------------------------------- 1 | tracer_new <- function( 2 | provider, 3 | name = NULL, 4 | version = NULL, 5 | schema_url = NULL, 6 | attributes = NULL, 7 | ... 8 | ) { 9 | name <- as_string(name, null = TRUE) 10 | inst_scope <- find_instrumentation_scope(name) 11 | name <- name %||% inst_scope[["name"]] 12 | if (!inst_scope[["on"]]) { 13 | return(otel::tracer_provider_noop$new()$get_tracer(name)) 14 | } 15 | 16 | self <- new_object( 17 | "otel_tracer", 18 | start_span = function( 19 | name = NULL, 20 | attributes = NULL, 21 | links = NULL, 22 | options = NULL 23 | ) { 24 | span_new( 25 | self, 26 | name = name, 27 | attributes = attributes, 28 | links = links, 29 | options = options, 30 | scope = NULL, 31 | activation_scope = NULL 32 | ) 33 | }, 34 | start_local_active_span = function( 35 | name = NULL, 36 | attributes = NULL, 37 | links = NULL, 38 | options = NULL, 39 | scope = parent.frame(), 40 | activation_scope = parent.frame() 41 | ) { 42 | span_new( 43 | self, 44 | name = name, 45 | attributes = attributes, 46 | links = links, 47 | options = options, 48 | scope = scope, 49 | activation_scope = activation_scope 50 | ) 51 | }, 52 | is_enabled = function(...) TRUE, 53 | get_active_span_context = function() { 54 | xptr <- ccall(otel_get_active_span_context, self$xptr) 55 | span_context_new(xptr) 56 | }, 57 | get_active_span = function() { 58 | xptr <- ccall(otel_get_active_span, self$xptr) 59 | span_base_new(self, xptr) 60 | }, 61 | flush = function() { 62 | self$provider$flush() 63 | }, 64 | extract_http_context = function(headers) { 65 | headers <- as_http_context_headers(headers) 66 | xptr <- ccall(otel_extract_http_context, headers) 67 | span_context_new(xptr) 68 | } 69 | ) 70 | self$provider <- provider 71 | self$name <- as_string(name) 72 | self$version <- as_string(version) 73 | self$schema_url <- as_string(schema_url) 74 | self$attributes <- as_otel_attributes(attributes) 75 | self$xptr <- ccall( 76 | otel_get_tracer, 77 | self$provider$xptr, 78 | self$name, 79 | self$version, 80 | self$schema_url, 81 | self$attributes 82 | ) 83 | self 84 | } 85 | -------------------------------------------------------------------------------- /R/tracer-provider-file.R: -------------------------------------------------------------------------------- 1 | tracer_provider_file_new <- function(opts = NULL) { 2 | opts <- as_tracer_provider_file_options(opts) 3 | 4 | self <- new_object( 5 | c("otel_tracer_provider_file", "otel_tracer_provider"), 6 | get_tracer = function( 7 | name = NULL, 8 | version = NULL, 9 | schema_url = NULL, 10 | attributes = NULL, 11 | ... 12 | ) { 13 | tracer_new(self, name, version, schema_url, attributes, ...) 14 | }, 15 | flush = function() { 16 | ccall(otel_tracer_provider_flush, self$xptr) 17 | } 18 | ) 19 | 20 | attributes <- as_otel_attributes(the$default_resource_attributes) 21 | self$xptr <- ccall(otel_create_tracer_provider_file, opts, attributes) 22 | self 23 | } 24 | 25 | #' Tracer provider to write traces into a JSONL file 26 | #' 27 | #' @description 28 | #' This is the [OTLP file exporter]( 29 | #' https://opentelemetry.io/docs/specs/otel/protocol/file-exporter/). 30 | #' It writes spans to a JSONL file, each span is a line in the file, 31 | #' a valid JSON value. The line separator is `\n`. The preferred file 32 | #' extension is `jsonl`. 33 | #' 34 | #' Select this tracer provider with `OTEL_TRACES_EXPORTER=otlp/file`. 35 | #' 36 | #' # Usage 37 | #' 38 | #' Externally: 39 | #' ``` 40 | #' OTEL_TRACES_EXPORTER=otlp/file 41 | #' ``` 42 | #' 43 | #' From R: 44 | #' ``` 45 | #' tracer_provider_file$new(opts = NULL) 46 | #' tracer_provider_file$options() 47 | #' ``` 48 | #' 49 | #' # Arguments 50 | #' 51 | #' - `opts`: Named list of options. See below. 52 | #' 53 | #' # Options 54 | #' 55 | #' ## File exporter options 56 | #' 57 | #' ```{r} 58 | #' #| echo: FALSE 59 | #' #| results: asis 60 | #' cat(doc_file_exporter_options( 61 | #' tracer_provider_file_options_evs(), 62 | #' tracer_provider_file$options() 63 | #' )) 64 | #' ``` 65 | #' 66 | #' @return 67 | #' `tracer_provider_file$new()` returns an [otel::otel_tracer_provider] 68 | #' object. 69 | #' 70 | #' `tracer_provider_file$options()` returns a named list, the current 71 | #' values of the options. 72 | #' 73 | #' @format NULL 74 | #' @usage NULL 75 | #' @export 76 | #' @examples 77 | #' tracer_provider_file$options() 78 | 79 | tracer_provider_file <- list( 80 | new = tracer_provider_file_new, 81 | options = function() { 82 | utils::modifyList( 83 | as_tracer_provider_file_options(NULL), 84 | ccall(otel_tracer_provider_file_options_defaults) 85 | ) 86 | } 87 | ) 88 | -------------------------------------------------------------------------------- /R/logger-provider-file.R: -------------------------------------------------------------------------------- 1 | logger_provider_file_new <- function(opts = NULL) { 2 | opts <- as_logger_provider_file_options(opts) 3 | 4 | self <- new_object( 5 | c("otel_logger_provider_file", "otel_logger_provider"), 6 | get_logger = function( 7 | name = NULL, 8 | minimum_severity = NULL, 9 | version = NULL, 10 | schema_url = NULL, 11 | attributes = NULL, 12 | ... 13 | ) { 14 | logger_new( 15 | self, 16 | name, 17 | minimum_severity, 18 | version, 19 | schema_url, 20 | attributes, 21 | ... 22 | ) 23 | }, 24 | flush = function() { 25 | ccall(otel_logger_provider_flush, self$xptr) 26 | } 27 | ) 28 | 29 | self$xptr <- ccall(otel_create_logger_provider_file, opts) 30 | self 31 | } 32 | 33 | #' Logger provider to write log messages into a JSONL file. 34 | #' 35 | #' @description 36 | #' This is the [OTLP file exporter]( 37 | #' https://opentelemetry.io/docs/specs/otel/protocol/file-exporter/). 38 | #' It writes logs to a JSONL file, each log is a line in the file, 39 | #' a valid JSON value. The line separator is `\n`. The preferred file 40 | #' extension is `jsonl`. 41 | #' 42 | #' Select this tracer provider with `OTEL_LOGS_EXPORTER=otlp/file`. 43 | #' 44 | #' # Usage 45 | #' 46 | #' Externally: 47 | #' ``` 48 | #' OTEL_LOGS_EXPORTER=otlp/file 49 | #' ``` 50 | #' 51 | #' From R: 52 | #' ``` 53 | #' logger_provider_file$new(opts = NULL) 54 | #' logger_provider_file$options() 55 | #' ``` 56 | #' 57 | #' # Arguments 58 | #' 59 | #' - `opts`: Named list of options. See below. 60 | #' 61 | #' # Options 62 | #' 63 | #' ## File exporter options 64 | #' 65 | #' ```{r} 66 | #' #| echo: FALSE 67 | #' #| results: asis 68 | #' cat(doc_file_exporter_options( 69 | #' logger_provider_file_options_evs(), 70 | #' logger_provider_file$options() 71 | #' )) 72 | #' ``` 73 | #' 74 | #' @return 75 | #' `logger_provider_file$new()` returns an [otel::otel_logger_provider] 76 | #' object. 77 | #' 78 | #' `logger_provider_file$options()` returns a named list, the current 79 | #' values of the options. 80 | #' 81 | #' @format NULL 82 | #' @usage NULL 83 | #' @export 84 | #' @examples 85 | #' logger_provider_file$options() 86 | 87 | logger_provider_file <- list( 88 | new = logger_provider_file_new, 89 | options = function() { 90 | utils::modifyList( 91 | as_logger_provider_file_options(NULL), 92 | ccall(otel_logger_provider_file_options_defaults) 93 | ) 94 | } 95 | ) 96 | -------------------------------------------------------------------------------- /tests/testthat/test-glue.R: -------------------------------------------------------------------------------- 1 | test_that("glue", { 2 | # no input 3 | expect_equal(glue(NULL), "") 4 | 5 | # plain text 6 | expect_equal(glue("foo"), "foo") 7 | 8 | # trimming 9 | expect_equal( 10 | glue( 11 | " foo \\ 12 | bar 13 | baz" 14 | ), 15 | "foo bar\nbaz" 16 | ) 17 | 18 | # interpolation 19 | expect_equal(glue("1+1={1+1}"), "1+1=2") 20 | }) 21 | 22 | test_that("escaped delimiters", { 23 | expect_equal(glue("this {{ is verbatim }}"), "this { is verbatim }") 24 | }) 25 | 26 | test_that("escaping", { 27 | expect_equal(glue("foo {'\\t\\t'} bar", .cli = TRUE), "foo \t\t bar") 28 | expect_equal(glue('foo {"\\t\\t"} bar', .cli = TRUE), "foo \t\t bar") 29 | expect_equal( 30 | glue("foo {`\\t\\t`} bar", .cli = TRUE, .transformer = function(x, ...) x), 31 | "foo `\\t\\t` bar" 32 | ) 33 | expect_equal( 34 | glue("foo { # c } bar", .cli = TRUE, .transformer = function(x, ...) x), 35 | "foo # c bar" 36 | ) 37 | 38 | expect_equal( 39 | glue("'fo`o\"#", .cli = TRUE, .transformer = function(x, ...) x), 40 | "'fo`o\"#" 41 | ) 42 | expect_equal( 43 | glue('"fo`o\'#', .cli = TRUE, .transformer = function(x, ...) x), 44 | '"fo`o\'#' 45 | ) 46 | expect_equal( 47 | glue("# foo", .transformer = function(x, ...) x), 48 | "# foo" 49 | ) 50 | expect_equal( 51 | glue("x {# foo } y", .cli = TRUE, .transformer = function(x, ...) x), 52 | "x # foo y" 53 | ) 54 | }) 55 | 56 | test_that("delim levels", { 57 | expect_equal(glue("{ '{ 1 + 1 }' }"), "{ 1 + 1 }") 58 | }) 59 | 60 | test_that("glue errors", { 61 | expect_snapshot(error = TRUE, { 62 | glue("{ no brace") 63 | glue("{ 'no quote ", .cli = TRUE) 64 | glue('{ "no dquote ', .cli = TRUE) 65 | glue('{ `no backtick ', .cli = TRUE) 66 | }) 67 | }) 68 | 69 | test_that("trim", { 70 | # skip first newline 71 | expect_snapshot({ 72 | trim("\nfoo") 73 | }) 74 | 75 | # ignore last empty line for indent 76 | expect_snapshot({ 77 | trim("foo\n bar") 78 | trim("foo\n bar\n ") 79 | }) 80 | 81 | # copy line of space if shorter than minimum indent 82 | expect_snapshot({ 83 | trim("foo\n bar\n \nbaz") 84 | }) 85 | 86 | # A line shorter than min_indent that contains only indentation should not be 87 | # trimmed, removed, or prepended to the next line. 88 | expect_snapshot({ 89 | trim( 90 | " 91 | \ta 92 | \tb 93 | \t 94 | \tc" 95 | ) 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /src/errors.c: -------------------------------------------------------------------------------- 1 | 2 | #include "errors.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | #define ERRORBUF_SIZE 4096 9 | static char errorbuf[ERRORBUF_SIZE]; 10 | 11 | SEXP r_throw_error(const char *func, const char *filename, int line, 12 | const char *msg, ...) { 13 | va_list args; 14 | errorbuf[0] = '\0'; 15 | va_start(args, msg); 16 | vsnprintf(errorbuf, ERRORBUF_SIZE, msg, args); 17 | va_end (args); 18 | error("%s @%s:%d (%s)", errorbuf, filename, line, func); 19 | return R_NilValue; 20 | } 21 | 22 | #ifdef _WIN32 23 | 24 | SEXP r_throw_system_error(const char *func, const char *filename, int line, 25 | DWORD errorcode, const char *sysmsg, 26 | const char *msg, ...) { 27 | 28 | va_list args; 29 | LPVOID lpMsgBuf; 30 | char *realsysmsg = sysmsg ? (char*) sysmsg : NULL; 31 | char *failmsg = "Formatting the system message failed :("; 32 | 33 | if (errorcode == -1) errorcode = GetLastError(); 34 | 35 | if (!realsysmsg) { 36 | DWORD ret = FormatMessage( 37 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 38 | FORMAT_MESSAGE_FROM_SYSTEM | 39 | FORMAT_MESSAGE_IGNORE_INSERTS, 40 | NULL, 41 | errorcode, 42 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 43 | (LPTSTR) &lpMsgBuf, 44 | 0, NULL); 45 | 46 | if (ret == 0) { 47 | realsysmsg = failmsg; 48 | } else { 49 | realsysmsg = R_alloc(1, strlen(lpMsgBuf) + 1); 50 | strcpy(realsysmsg, lpMsgBuf); 51 | LocalFree(lpMsgBuf); 52 | } 53 | } 54 | 55 | errorbuf[0] = '\0'; 56 | va_start(args, msg); 57 | vsnprintf(errorbuf, ERRORBUF_SIZE, msg, args); 58 | va_end(args); 59 | error("%s (system error %ld, %s) @%s:%d (%s)", errorbuf, errorcode, 60 | realsysmsg, filename, line, func); 61 | return R_NilValue; 62 | } 63 | 64 | #endif 65 | 66 | #ifdef _WIN32 67 | SEXP r_throw_posix_error( 68 | #else 69 | SEXP r_throw_system_error( 70 | #endif 71 | const char *func, const char *filename, int line, 72 | int errorcode, const char *sysmsg, 73 | const char *msg, ...) { 74 | va_list args; 75 | if (!sysmsg) sysmsg = strerror(errorcode); 76 | errorbuf[0] = '\0'; 77 | va_start(args, msg); 78 | vsnprintf(errorbuf, ERRORBUF_SIZE, msg, args); 79 | va_end(args); 80 | error("%s (system error %d, %s) @%s:%d (%s)", errorbuf, errorcode, sysmsg, 81 | filename, line, func); 82 | return R_NilValue; 83 | } 84 | -------------------------------------------------------------------------------- /R/meter-provider-stdout.R: -------------------------------------------------------------------------------- 1 | meter_provider_stdstream_new <- function(opts = NULL) { 2 | opts <- as_meter_provider_stdstream_options(opts) 3 | self <- new_object( 4 | c("otel_meter_provider_stdstream", "otel_meter_provider"), 5 | get_meter = function( 6 | name = NULL, 7 | version = NULL, 8 | schema_url = NULL, 9 | attributes = NULL, 10 | ... 11 | ) { 12 | meter_new(self, name, version, schema_url, attributes, ...) 13 | }, 14 | flush = function() { 15 | ccall(otel_meter_provider_flush, self$xptr, NULL) 16 | }, 17 | shutdown = function() { 18 | ccall(otel_meter_provider_shutdown, self$xptr, NULL) 19 | invisible(self) 20 | } 21 | ) 22 | 23 | attributes <- as_otel_attributes(the$default_resource_attributes) 24 | self$xptr <- ccall(otel_create_meter_provider_stdstream, opts, attributes) 25 | self 26 | } 27 | 28 | meter_provider_stdstream_options <- function() { 29 | as_meter_provider_stdstream_options(NULL) 30 | } 31 | 32 | #' Meter provider to write to the standard output or standard error or 33 | #' to a file 34 | #' 35 | #' @description 36 | #' Writes metrics measurements to the standard output or error, or to a 37 | #' file. Useful for debugging. 38 | #' 39 | #' # Usage 40 | #' 41 | #' Externally: 42 | #' ``` 43 | #' OTEL_METRICS_EXPORTER=console 44 | #' OTEL_METRICS_EXPORTER=stderr 45 | #' ``` 46 | #' 47 | #' From R: 48 | #' ``` 49 | #' meter_provider_stdstream$new(opts = NULL) 50 | #' meter_provider_stdstream$options() 51 | #' ``` 52 | #' 53 | #' # Arguments 54 | #' 55 | #' `opts`: Named list of options. See below. 56 | #' 57 | #' # Options 58 | #' 59 | #' ## Standard stream exporter options 60 | #' 61 | #' ```{r} 62 | #' #| echo: FALSE 63 | #' #| results: asis 64 | #' cat(doc_stdstream_exporter_options( 65 | #' meter_provider_stdstream_options_evs() 66 | #' )) 67 | #' ``` 68 | #' 69 | #' ## Metric reader options 70 | #' 71 | #' ```{r} 72 | #' #| echo: FALSE 73 | #' #| results: asis 74 | #' cat(doc_metric_reader_options()) 75 | #' ``` 76 | #' 77 | #' @return 78 | #' `meter_provider_stdstream$new()` returns an [otel::otel_meter_provider] 79 | #' object. 80 | #' 81 | #' `meter_provider_stdstream$options()` returns a named list, the current 82 | #' values of the options. 83 | #' 84 | #' @format NULL 85 | #' @usage NULL 86 | #' @export 87 | #' @examples 88 | #' meter_provider_stdstream$options() 89 | 90 | meter_provider_stdstream <- list( 91 | new = meter_provider_stdstream_new, 92 | options = meter_provider_stdstream_options 93 | ) 94 | -------------------------------------------------------------------------------- /R/tracer-provider-memory.R: -------------------------------------------------------------------------------- 1 | tracer_provider_memory_new <- function(opts = NULL) { 2 | opts <- as_tracer_provider_memory_options(opts) 3 | self <- new_object( 4 | c("otel_tracer_provider_memory", "otel_tracer_provider"), 5 | get_tracer = function( 6 | name = NULL, 7 | version = NULL, 8 | schema_url = NULL, 9 | attributes = NULL, 10 | ... 11 | ) { 12 | tracer_new(self, name, version, schema_url, attributes, ...) 13 | }, 14 | flush = function() { 15 | # noop currently 16 | }, 17 | get_spans = function() { 18 | spans <- ccall(otel_tracer_provider_memory_get_spans, self$xptr) 19 | names(spans) <- map_chr(spans, function(x) x[["name"]] %||% "") 20 | spans 21 | } 22 | ) 23 | 24 | attributes <- as_otel_attributes(the$default_resource_attributes) 25 | self$xptr <- ccall(otel_create_tracer_provider_memory, opts, attributes) 26 | self 27 | } 28 | 29 | tracer_provider_memory_options <- function() { 30 | as_tracer_provider_memory_options(NULL) 31 | } 32 | 33 | #' In-memory tracer provider for testing 34 | #' 35 | #' @description 36 | #' Collects spans in memory. This is useful for testing your instrumented 37 | #' R package or application. 38 | #' 39 | #' [with_otel_record()] uses this tracer provider. 40 | #' Use [with_otel_record()] in your tests to record telemetry and check 41 | #' that it is correct. 42 | #' 43 | #' # Usage 44 | #' 45 | #' ``` 46 | #' tp <- tracer_provider_memory$new(opts = NULL) 47 | #' tp$get_spans() 48 | #' tracer_provider_memory$options() 49 | #' ``` 50 | #' 51 | #' `tp$get_spans()` erases the internal buffer of the tracer provider. 52 | #' 53 | #' # Arguments 54 | #' 55 | #' - `opts`: Named list of options. See below. 56 | #' 57 | #' # Options 58 | #' 59 | #' ## Memory exporter options 60 | #' 61 | #' ```{r} 62 | #' #| echo: FALSE 63 | #' #| results: asis 64 | #' cat(doc_memory_exporter_options(tracer_provider_memory_options_evs())) 65 | #' ``` 66 | #' 67 | #' @return 68 | #' `tracer_provider_memory$new()` returns an [otel::otel_tracer_provider] 69 | #' object. `tp$get_spans()` returns a named list of recorded spans, with 70 | #' the span names as names. 71 | #' 72 | #' `tracer_provider_memory$options()` returns a named list, the current 73 | #' values for all options. 74 | #' 75 | #' @usage NULL 76 | #' @format NULL 77 | #' @export 78 | #' @examples 79 | #' tracer_provider_memory$options() 80 | 81 | tracer_provider_memory <- list( 82 | new = tracer_provider_memory_new, 83 | options = tracer_provider_memory_options 84 | ) 85 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | workflow_dispatch: 12 | 13 | name: R-CMD-check.yaml 14 | 15 | permissions: read-all 16 | 17 | jobs: 18 | R-CMD-check: 19 | runs-on: ${{ matrix.config.os }} 20 | 21 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | config: 27 | - {os: macos-latest, r: 'release' } 28 | - {os: macos-15-intel, r: 'release' } 29 | 30 | - {os: windows-latest, r: 'devel' } 31 | - {os: windows-latest, r: 'release' } 32 | - {os: windows-latest, r: 'oldrel-1' } 33 | - {os: windows-latest, r: 'oldrel-2'} 34 | # - {os: windows-latest, r: 'oldrel-3'} 35 | # - {os: windows-latest, r: 'oldrel-4'} 36 | 37 | - {os: ubuntu-latest, r: 'devel' } 38 | - {os: ubuntu-latest, r: 'release' } 39 | - {os: ubuntu-latest, r: 'oldrel-1'} 40 | - {os: ubuntu-latest, r: 'oldrel-2'} 41 | - {os: ubuntu-latest, r: 'oldrel-3'} 42 | - {os: ubuntu-latest, r: 'oldrel-4'} 43 | 44 | env: 45 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 46 | R_KEEP_PKG_SOURCE: yes 47 | 48 | steps: 49 | - uses: actions/checkout@v4 50 | 51 | - uses: r-lib/actions/setup-pandoc@v2 52 | 53 | - uses: posit-dev/setup-air@v1 54 | if: ${{ runner.os == 'macOS' }} 55 | 56 | - uses: r-lib/actions/setup-r@v2 57 | with: 58 | r-version: ${{ matrix.config.r }} 59 | http-user-agent: ${{ matrix.config.http-user-agent }} 60 | use-public-rspm: true 61 | Ncpus: 4 62 | 63 | - uses: r-lib/actions/setup-r-dependencies@v2 64 | with: 65 | extra-packages: any::rcmdcheck, gaborcsardi/testthatlabs 66 | needs: check 67 | 68 | - uses: r-hub/actions/debug-shell@main 69 | 70 | - uses: r-hub/actions/setup-r-sysreqs@v1 71 | 72 | - uses: r-lib/actions/check-r-package@v2 73 | with: 74 | upload-snapshots: true 75 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 76 | -------------------------------------------------------------------------------- /src/extra/cmake/R-4.3/protobuf-targets-release.cmake: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------- 2 | # Generated CMake target import file for configuration "Release". 3 | #---------------------------------------------------------------- 4 | 5 | # Commands may need to know the format version. 6 | set(CMAKE_IMPORT_FILE_VERSION 1) 7 | 8 | # Import target "protobuf::libprotobuf-lite" for configuration "Release" 9 | set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) 10 | set_target_properties(protobuf::libprotobuf-lite PROPERTIES 11 | IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" 12 | IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libprotobuf-lite.a" 13 | ) 14 | 15 | list(APPEND _cmake_import_check_targets protobuf::libprotobuf-lite ) 16 | list(APPEND _cmake_import_check_files_for_protobuf::libprotobuf-lite "${_IMPORT_PREFIX}/lib/libprotobuf-lite.a" ) 17 | 18 | # Import target "protobuf::libprotobuf" for configuration "Release" 19 | set_property(TARGET protobuf::libprotobuf APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) 20 | set_target_properties(protobuf::libprotobuf PROPERTIES 21 | IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" 22 | IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libprotobuf.a" 23 | ) 24 | 25 | list(APPEND _cmake_import_check_targets protobuf::libprotobuf ) 26 | list(APPEND _cmake_import_check_files_for_protobuf::libprotobuf "${_IMPORT_PREFIX}/lib/libprotobuf.a" ) 27 | 28 | # Import target "protobuf::libprotoc" for configuration "Release" 29 | set_property(TARGET protobuf::libprotoc APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) 30 | set_target_properties(protobuf::libprotoc PROPERTIES 31 | IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" 32 | IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libprotoc.a" 33 | ) 34 | 35 | list(APPEND _cmake_import_check_targets protobuf::libprotoc ) 36 | list(APPEND _cmake_import_check_files_for_protobuf::libprotoc "${_IMPORT_PREFIX}/lib/libprotoc.a" ) 37 | 38 | # Import target "protobuf::protoc" for configuration "Release" 39 | set_property(TARGET protobuf::protoc APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) 40 | set_target_properties(protobuf::protoc PROPERTIES 41 | IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/bin/protoc.exe-25.2.0.exe" 42 | ) 43 | 44 | list(APPEND _cmake_import_check_targets protobuf::protoc ) 45 | list(APPEND _cmake_import_check_files_for_protobuf::protoc "${_IMPORT_PREFIX}/bin/protoc.exe-25.2.0.exe" ) 46 | 47 | # Commands beyond this point should not need to know the version. 48 | set(CMAKE_IMPORT_FILE_VERSION) 49 | -------------------------------------------------------------------------------- /src/extra/cmake/R-4.4/protobuf-targets-release.cmake: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------- 2 | # Generated CMake target import file for configuration "Release". 3 | #---------------------------------------------------------------- 4 | 5 | # Commands may need to know the format version. 6 | set(CMAKE_IMPORT_FILE_VERSION 1) 7 | 8 | # Import target "protobuf::libprotobuf-lite" for configuration "Release" 9 | set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) 10 | set_target_properties(protobuf::libprotobuf-lite PROPERTIES 11 | IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" 12 | IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libprotobuf-lite.a" 13 | ) 14 | 15 | list(APPEND _cmake_import_check_targets protobuf::libprotobuf-lite ) 16 | list(APPEND _cmake_import_check_files_for_protobuf::libprotobuf-lite "${_IMPORT_PREFIX}/lib/libprotobuf-lite.a" ) 17 | 18 | # Import target "protobuf::libprotobuf" for configuration "Release" 19 | set_property(TARGET protobuf::libprotobuf APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) 20 | set_target_properties(protobuf::libprotobuf PROPERTIES 21 | IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" 22 | IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libprotobuf.a" 23 | ) 24 | 25 | list(APPEND _cmake_import_check_targets protobuf::libprotobuf ) 26 | list(APPEND _cmake_import_check_files_for_protobuf::libprotobuf "${_IMPORT_PREFIX}/lib/libprotobuf.a" ) 27 | 28 | # Import target "protobuf::libprotoc" for configuration "Release" 29 | set_property(TARGET protobuf::libprotoc APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) 30 | set_target_properties(protobuf::libprotoc PROPERTIES 31 | IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" 32 | IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libprotoc.a" 33 | ) 34 | 35 | list(APPEND _cmake_import_check_targets protobuf::libprotoc ) 36 | list(APPEND _cmake_import_check_files_for_protobuf::libprotoc "${_IMPORT_PREFIX}/lib/libprotoc.a" ) 37 | 38 | # Import target "protobuf::protoc" for configuration "Release" 39 | set_property(TARGET protobuf::protoc APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) 40 | set_target_properties(protobuf::protoc PROPERTIES 41 | IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/bin/protoc.exe-29.3.0.exe" 42 | ) 43 | 44 | list(APPEND _cmake_import_check_targets protobuf::protoc ) 45 | list(APPEND _cmake_import_check_files_for_protobuf::protoc "${_IMPORT_PREFIX}/bin/protoc.exe-29.3.0.exe" ) 46 | 47 | # Commands beyond this point should not need to know the version. 48 | set(CMAKE_IMPORT_FILE_VERSION) 49 | -------------------------------------------------------------------------------- /R/record.R: -------------------------------------------------------------------------------- 1 | otel_save_cache <- function() { 2 | asNamespace("otel")$otel_save_cache() 3 | } 4 | 5 | otel_restore_cache <- function(copy) { 6 | asNamespace("otel")$otel_restore_cache(copy) 7 | } 8 | 9 | #' Record OpenTelemetry output, for testing purposes 10 | #' 11 | #' You can use this function to test that OpenTelemetry output is 12 | #' correctly generated for your package or application. 13 | #' 14 | #' It evaluates the supplied expression, collects OpenTelemetry output 15 | #' from it and returns it. 16 | #' 17 | #' Note: `with_otel_record()` cannot record logs yet. 18 | #' 19 | #' `with_otel_record()` uses [tracer_provider_memory] and 20 | #' [meter_provider_memory] internally. 21 | #' 22 | #' @param expr Expression to evaluate. 23 | #' @param what Character vector, type(s) of OpenTelemetry output to collect. 24 | #' @param tracer_opts Named list of options to pass to the tracer provider. 25 | #' @param meter_opts Named list of options to pass to the meter provider. 26 | #' @return A list with the output for each output type. Entries: 27 | #' * `value`: value of `expr`. 28 | #' * `traces`: the recorded spans, if requested in `what`. 29 | #' * `metrics`: the recorded metrics measurements, if requested in `what`. 30 | #' 31 | #' @export 32 | #' @examples 33 | #' spns <- with_otel_record({ 34 | #' trc <- otel::get_tracer("mytracer") 35 | #' spn1 <- trc$start_local_active_span() 36 | #' spn2 <- trc$start_local_active_span("my") 37 | #' spn2$end() 38 | #' spn1$end() 39 | #' NULL 40 | #' }) 41 | #' spns 42 | 43 | with_otel_record <- function( 44 | expr, 45 | what = c("traces", "metrics"), 46 | tracer_opts = list(), 47 | meter_opts = list() 48 | ) { 49 | # save current otel cache, restore on exit 50 | copy <- otel_save_cache() 51 | on.exit(otel_restore_cache(copy), add = TRUE) 52 | 53 | # create new providers 54 | tmp <- new.env(parent = emptyenv()) 55 | if ("traces" %in% what) { 56 | tmp[["tracer_provider"]] <- tracer_provider_memory_new(tracer_opts) 57 | } 58 | if ("metrics" %in% what) { 59 | tmp[["meter_provider"]] <- meter_provider_memory_new(meter_opts) 60 | on.exit(tmp[["meter_provider"]]$shutdown(), add = TRUE) 61 | } 62 | otel_restore_cache(tmp) 63 | 64 | # record 65 | if (is.function(value <- expr)) { 66 | value <- value() 67 | } 68 | 69 | # return recorded results 70 | list( 71 | value = value, 72 | traces = if ("traces" %in% what) { 73 | tmp[["tracer_provider"]]$get_spans() 74 | }, 75 | metrics = if ("metrics" %in% what) { 76 | tmp[["meter_provider"]]$get_metrics() 77 | } 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /man/meter_provider_stdstream.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/meter-provider-stdout.R 3 | \docType{data} 4 | \name{meter_provider_stdstream} 5 | \alias{meter_provider_stdstream} 6 | \title{Meter provider to write to the standard output or standard error or 7 | to a file} 8 | \value{ 9 | \code{meter_provider_stdstream$new()} returns an \link[otel:otel_meter_provider]{otel::otel_meter_provider} 10 | object. 11 | 12 | \code{meter_provider_stdstream$options()} returns a named list, the current 13 | values of the options. 14 | } 15 | \description{ 16 | Writes metrics measurements to the standard output or error, or to a 17 | file. Useful for debugging. 18 | } 19 | \section{Usage}{ 20 | Externally: 21 | 22 | \if{html}{\out{
}}\preformatted{OTEL_METRICS_EXPORTER=console 23 | OTEL_METRICS_EXPORTER=stderr 24 | }\if{html}{\out{
}} 25 | 26 | From R: 27 | 28 | \if{html}{\out{
}}\preformatted{meter_provider_stdstream$new(opts = NULL) 29 | meter_provider_stdstream$options() 30 | }\if{html}{\out{
}} 31 | } 32 | 33 | \section{Arguments}{ 34 | \code{opts}: Named list of options. See below. 35 | } 36 | 37 | \section{Options}{ 38 | \subsection{Standard stream exporter options}{ 39 | \itemize{ 40 | \item \code{output}: where to write the output. Can be 41 | \itemize{ 42 | \item \code{"stdout"}: write output to the standard output, 43 | \item \code{"stderr"}: write output to the standard error, 44 | \item another string: write output to a file. (To write output to a file 45 | named \code{"stdout"} or \code{"stderr"}, use a \verb{./} prefix.) 46 | } 47 | 48 | Value is set from 49 | \itemize{ 50 | \item the \code{opts} argument, or 51 | \item the \code{OTEL_R_EXPORTER_STDSTREAM_METRICS_OUTPUT} environment variable, or 52 | \item the \code{OTEL_R_EXPORTER_STDSTREAM_OUTPUT} environment variable, or 53 | \item the default is \code{"stdout"}. 54 | } 55 | } 56 | } 57 | 58 | \subsection{Metric reader options}{ 59 | \itemize{ 60 | \item \code{export_interval}: the time interval between the 61 | start of two export attempts, in milliseconds. Value is set from 62 | \itemize{ 63 | \item the \code{opts} argument, or 64 | \item the \code{OTEL_METRIC_EXPORT_INTERVAL} environment variable, or 65 | \item the default is \code{60000}. 66 | } 67 | \item \code{export_timeout}: Maximum allowed time to export data, in 68 | milliseconds. Value is set from 69 | \itemize{ 70 | \item the \code{opts} argument, or 71 | \item the \code{OTEL_METRIC_EXPORT_TIMEOUT} environment variable, or 72 | \item the default is \code{30000}. 73 | } 74 | } 75 | } 76 | } 77 | 78 | \examples{ 79 | meter_provider_stdstream$options() 80 | } 81 | \keyword{datasets} 82 | -------------------------------------------------------------------------------- /src/otel_common_r.h: -------------------------------------------------------------------------------- 1 | #ifndef OTEL_R_COMMON_H 2 | #define OTEL_R_COMMON_H 3 | 4 | #include "otel_common.h" 5 | 6 | extern SEXP otel_span_kinds; 7 | extern SEXP otel_span_status_codes; 8 | 9 | SEXP rf_get_list_element(SEXP list, const char *str); 10 | 11 | void otel_span_context_finally(SEXP x); 12 | 13 | void r2c_otel_string(SEXP s, struct otel_string *cs); 14 | 15 | void r2c_attribute( 16 | const char *name, SEXP value, struct otel_attribute *attr); 17 | void r2c_attributes(SEXP r, struct otel_attributes *c); 18 | 19 | SEXP c2r_otel_string(const struct otel_string *s); 20 | SEXP c2r_otel_strings(const struct otel_strings *s); 21 | SEXP c2r_otel_named_strings(const struct otel_strings *s); 22 | 23 | SEXP c2r_otel_double_array(const struct otel_double_array *a); 24 | 25 | SEXP c2r_otel_trace_flags(const struct otel_trace_flags *flags); 26 | 27 | SEXP c2r_otel_instrumentation_scope( 28 | const struct otel_instrumentation_scope *is); 29 | 30 | SEXP c2r_otel_attributes(const struct otel_attributes *attrs); 31 | SEXP c2r_otel_events(const struct otel_events *events); 32 | SEXP c2r_otel_span_links(const struct otel_span_links *links); 33 | 34 | SEXP c2r_otel_sum_point_data(struct otel_sum_point_data *d); 35 | SEXP c2r_otel_histogram_point_data(struct otel_histogram_point_data *d); 36 | SEXP c2r_otel_last_value_point_data(struct otel_last_value_point_data *d); 37 | SEXP c2r_otel_drop_point_data(struct otel_drop_point_data *d); 38 | SEXP c2r_otel_point_data_attributes(struct otel_point_data_attributes *pda); 39 | SEXP c2r_otel_metric_data(struct otel_metric_data *d); 40 | SEXP c2r_otel_scope_metrics(struct otel_scope_metrics *sm); 41 | SEXP c2r_otel_resource_metrics(struct otel_resource_metrics *rm); 42 | SEXP c2r_otel_metrics_data(const struct otel_metrics_data *data); 43 | 44 | SEXP c2r_otel_collector_log_record(const struct otel_collector_log_record *lr); 45 | SEXP c2r_otel_collector_scope_log(const struct otel_collector_scope_log *sl); 46 | SEXP c2r_otel_collector_resource_log( 47 | const struct otel_collector_resource_log *rl); 48 | SEXP c2r_otel_collector_resource_logs( 49 | const struct otel_collector_resource_logs *rl); 50 | 51 | SEXP c2r_otel_collector_scope_metric( 52 | const struct otel_collector_scope_metric *sl); 53 | SEXP c2r_otel_collector_resource_metric( 54 | const struct otel_collector_resource_metric *rm); 55 | SEXP c2r_otel_collector_resource_metrics( 56 | const struct otel_collector_resource_metrics *rl); 57 | 58 | void r2c_otel_file_exporter_options( 59 | SEXP options, struct otel_file_exporter_options *coptions); 60 | SEXP c2r_otel_file_exporter_options( 61 | const struct otel_file_exporter_options *o); 62 | 63 | void r2c_otel_http_exporter_options( 64 | SEXP options, struct otel_http_exporter_options *coptions); 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[r]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "Posit.air-vscode" 5 | }, 6 | "[quarto]": { 7 | "editor.formatOnSave": true, 8 | "editor.defaultFormatter": "quarto.quarto" 9 | }, 10 | "files.associations": { 11 | "new": "cpp", 12 | "otel_attributes.h": "c", 13 | "otel_common.h": "c", 14 | "string": "cpp", 15 | "__bit_reference": "cpp", 16 | "__hash_table": "cpp", 17 | "__locale": "cpp", 18 | "__node_handle": "cpp", 19 | "__split_buffer": "cpp", 20 | "__threading_support": "cpp", 21 | "__tree": "cpp", 22 | "__verbose_abort": "cpp", 23 | "any": "cpp", 24 | "array": "cpp", 25 | "bitset": "cpp", 26 | "cctype": "cpp", 27 | "charconv": "cpp", 28 | "clocale": "cpp", 29 | "cmath": "cpp", 30 | "complex": "cpp", 31 | "condition_variable": "cpp", 32 | "cstdarg": "cpp", 33 | "cstddef": "cpp", 34 | "cstdint": "cpp", 35 | "cstdio": "cpp", 36 | "cstdlib": "cpp", 37 | "cstring": "cpp", 38 | "ctime": "cpp", 39 | "cwchar": "cpp", 40 | "cwctype": "cpp", 41 | "deque": "cpp", 42 | "execution": "cpp", 43 | "memory": "cpp", 44 | "forward_list": "cpp", 45 | "fstream": "cpp", 46 | "future": "cpp", 47 | "initializer_list": "cpp", 48 | "iomanip": "cpp", 49 | "ios": "cpp", 50 | "iosfwd": "cpp", 51 | "iostream": "cpp", 52 | "istream": "cpp", 53 | "limits": "cpp", 54 | "list": "cpp", 55 | "locale": "cpp", 56 | "map": "cpp", 57 | "mutex": "cpp", 58 | "optional": "cpp", 59 | "ostream": "cpp", 60 | "print": "cpp", 61 | "queue": "cpp", 62 | "ratio": "cpp", 63 | "regex": "cpp", 64 | "span": "cpp", 65 | "sstream": "cpp", 66 | "stack": "cpp", 67 | "stdexcept": "cpp", 68 | "streambuf": "cpp", 69 | "string_view": "cpp", 70 | "tuple": "cpp", 71 | "typeinfo": "cpp", 72 | "unordered_map": "cpp", 73 | "unordered_set": "cpp", 74 | "valarray": "cpp", 75 | "variant": "cpp", 76 | "vector": "cpp", 77 | "algorithm": "cpp", 78 | "csignal": "cpp", 79 | "set": "cpp" 80 | }, 81 | "C_Cpp.default.includePath": [ 82 | "/Library/Frameworks/R.framework/Versions/Current/Headers/**", 83 | "src/cpp/api/include", 84 | "src/cpp/sdk/include", 85 | "src/cpp/exporters/memory/include/", 86 | "src/cpp/exporters/otlp/include/", 87 | "src/cpp/exporters/ostream/include/" 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | always_allow_html: yes 4 | editor_options: 5 | markdown: 6 | wrap: sentence 7 | --- 8 | 9 | 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>", 15 | fig.path = "man/figures/README-", 16 | out.width = "100%" 17 | ) 18 | ``` 19 | 20 | # otelsdk 21 | 22 | > OpenTelemetry SDK for R packages and projects 23 | 24 | 25 | ![lifecycle](https://lifecycle.r-lib.org/articles/figures/lifecycle-experimental.svg) 26 | [![R-CMD-check](https://github.com/r-lib/otelsdk/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/otelsdk/actions/workflows/R-CMD-check.yaml) 27 | [![Codecov test coverage](https://codecov.io/gh/r-lib/otelsdk/graph/badge.svg?token=GAqo3S38e7)](https://app.codecov.io/gh/r-lib/otelsdk) 28 | 29 | 30 | ```{r child = system.file(package = "otel", "dox", "intro.Rmd")} 31 | ``` 32 | 33 | To learn how to instrument your R code, see [Getting Started](`r gslink`) 34 | in the otel package. 35 | 36 | ```{r} 37 | #| include: FALSE 38 | ctlink <- if (Sys.getenv("IN_PKGDOWN") == "true") { 39 | "reference/collecting.html" 40 | } else { 41 | "https://otelsdk.r-lib.org/reference/collecting.html" 42 | } 43 | ``` 44 | 45 | To learn how to collect telemetry data from an instrumented R package or 46 | project, see [Collecting Telemetry Data](`r ctlink`). 47 | 48 | For project status, installation instructions and more, read on. 49 | 50 | ```{r child = system.file(package = "otel", "dox", "features.Rmd")} 51 | ``` 52 | 53 | ```{r child = system.file(package = "otel", "dox", "packages.Rmd")} 54 | ``` 55 | 56 | ```{r child = system.file(package = "otel", "dox", "reference-docs.Rmd")} 57 | ``` 58 | 59 | ```{r child = system.file(package = "otel", "dox", "status.Rmd")} 60 | ``` 61 | 62 | ```{r child = system.file(package = "otel", "dox", "version-support.Rmd")} 63 | ``` 64 | 65 | 66 | ## Installation 67 | 68 | You can install the otel from CRAN: 69 | 70 | ``` r 71 | install.packages("otelsdk") 72 | ``` 73 | 74 | ### Compiling from source 75 | 76 | To compile otelsdk from source, you need to install the protobuf 77 | library first: 78 | 79 | * On Windows install the correct version of [Rtools]( 80 | https://cran.r-project.org/bin/windows/Rtools/). 81 | * On Linux install the appropriate package from your distribution. 82 | * On macOS, you can use CRAN's [protobuf build]( 83 | https://mac.r-project.org/bin/) or Homebrew. If you are using 84 | CRAN's build, then you must uninstall or unlink Homebrew protobuf: 85 | ``` 86 | brew unlink protobuf 87 | ``` 88 | 89 | ```{r child = system.file(package = "otel", "dox", "repositories.Rmd")} 90 | ``` 91 | 92 | ## License 93 | 94 | MIT © Posit, PBC 95 | -------------------------------------------------------------------------------- /R/meter-provider-file.R: -------------------------------------------------------------------------------- 1 | meter_provider_file_new <- function(opts = NULL) { 2 | opts <- as_meter_provider_file_options(opts) 3 | 4 | self <- new_object( 5 | c("otel_meter_provider_file", "otel_meter_provider"), 6 | get_meter = function( 7 | name = NULL, 8 | version = NULL, 9 | schema_url = NULL, 10 | attributes = NULL, 11 | ... 12 | ) { 13 | meter_new(self, name, version, schema_url, attributes, ...) 14 | }, 15 | flush = function() { 16 | invisible(ccall(otel_meter_provider_flush, self$xptr, NULL)) 17 | }, 18 | shutdown = function() { 19 | ccall(otel_meter_provider_shutdown, self$xptr, NULL) 20 | invisible(self) 21 | } 22 | ) 23 | 24 | self$xptr <- ccall( 25 | otel_create_meter_provider_file, 26 | opts[["export_interval"]], 27 | opts[["export_timeout"]], 28 | opts 29 | ) 30 | self 31 | } 32 | 33 | #' Meter provider to collect metrics in JSONL files 34 | #' 35 | #' @description 36 | #' This is the [OTLP file exporter]( 37 | #' https://opentelemetry.io/docs/specs/otel/protocol/file-exporter/). 38 | #' It writes measurements to a JSONL file, each measurement is a line in 39 | #' the file, a valid JSON value. The line separator is `\n`. 40 | #' The preferred file extension is `jsonl`. 41 | #' 42 | #' Select this tracer provider with `OTEL_METRICS_EXPORTER=otlp/file`. 43 | #' 44 | #' # Usage 45 | #' 46 | #' Externally: 47 | #' ``` 48 | #' OTEL_METRICS_EXPORTER=otlp/file 49 | #' ``` 50 | #' 51 | #' From R: 52 | #' ``` 53 | #' meter_provider_file$new(opts = NULL) 54 | #' meter_provider_file$options() 55 | #' ``` 56 | #' 57 | #' # Arguments 58 | #' 59 | #' - `opts`: Named list of options. See below. 60 | #' 61 | #' # Options 62 | #' 63 | #' ## File exporter options 64 | #' 65 | #' ```{r} 66 | #' #| echo: FALSE 67 | #' #| results: asis 68 | #' cat(doc_file_exporter_options( 69 | #' meter_provider_file_options_evs(), 70 | #' meter_provider_file$options() 71 | #' )) 72 | #' ``` 73 | #' 74 | #' ## Metric reader options 75 | #' 76 | #' ```{r} 77 | #' #| echo: FALSE 78 | #' #| results: asis 79 | #' cat(doc_metric_reader_options()) 80 | #' ``` 81 | #' 82 | #' @return 83 | #' `meter_provider_file$new()` returns an [otel::otel_meter_provider] 84 | #' object. 85 | #' 86 | #' `meter_provider_file$options()` returns a named list, the current 87 | #' values of the options. 88 | #' 89 | #' @format NULL 90 | #' @usage NULL 91 | #' @export 92 | #' @examples 93 | #' meter_provider_file$options() 94 | 95 | meter_provider_file <- list( 96 | new = meter_provider_file_new, 97 | options = function() { 98 | utils::modifyList( 99 | as_meter_provider_file_options(NULL), 100 | ccall(otel_meter_provider_file_options_defaults) 101 | ) 102 | } 103 | ) 104 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: pr-commands.yaml 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | document: 13 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 14 | name: document 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | permissions: 19 | contents: write 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: r-lib/actions/pr-fetch@v2 24 | with: 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::roxygen2 34 | needs: pr-document 35 | 36 | - name: Document 37 | run: roxygen2::roxygenise() 38 | shell: Rscript {0} 39 | 40 | - name: commit 41 | run: | 42 | git config --local user.name "$GITHUB_ACTOR" 43 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 44 | git add man/\* NAMESPACE 45 | git commit -m 'Document' 46 | 47 | - uses: r-lib/actions/pr-push@v2 48 | with: 49 | repo-token: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | style: 52 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 53 | name: style 54 | runs-on: ubuntu-latest 55 | env: 56 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 57 | permissions: 58 | contents: write 59 | steps: 60 | - uses: actions/checkout@v4 61 | 62 | - uses: r-lib/actions/pr-fetch@v2 63 | with: 64 | repo-token: ${{ secrets.GITHUB_TOKEN }} 65 | 66 | - uses: r-lib/actions/setup-r@v2 67 | 68 | - name: Install dependencies 69 | run: install.packages("styler") 70 | shell: Rscript {0} 71 | 72 | - name: Style 73 | run: styler::style_pkg() 74 | shell: Rscript {0} 75 | 76 | - name: commit 77 | run: | 78 | git config --local user.name "$GITHUB_ACTOR" 79 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 80 | git add \*.R 81 | git commit -m 'Style' 82 | 83 | - uses: r-lib/actions/pr-push@v2 84 | with: 85 | repo-token: ${{ secrets.GITHUB_TOKEN }} 86 | -------------------------------------------------------------------------------- /src/extra/cmake/R-4.3/protobuf-config-version.cmake: -------------------------------------------------------------------------------- 1 | set(PACKAGE_VERSION "25.2.0") 2 | set(${PACKAGE_FIND_NAME}_VERSION_PRERELEASE "" PARENT_SCOPE) 3 | 4 | # Prerelease versions cannot be passed in directly via the find_package command, 5 | # so we allow users to specify it in a variable 6 | if(NOT DEFINED "${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE") 7 | set("${${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE}" "") 8 | else() 9 | set(PACKAGE_FIND_VERSION ${PACKAGE_FIND_VERSION}-${${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE}) 10 | endif() 11 | set(PACKAGE_FIND_VERSION_PRERELEASE "${${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE}") 12 | 13 | # VERSION_EQUAL ignores the prerelease strings, so we use STREQUAL. 14 | if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) 15 | set(PACKAGE_VERSION_EXACT TRUE) 16 | endif() 17 | 18 | set(PACKAGE_VERSION_COMPATIBLE TRUE) #Assume true until shown otherwise 19 | 20 | if(PACKAGE_FIND_VERSION) #Only perform version checks if one is given 21 | if(NOT PACKAGE_FIND_VERSION_MAJOR EQUAL "4") 22 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 23 | elseif(PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) 24 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 25 | elseif(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) 26 | # Do not match prerelease versions to non-prerelease version requests. 27 | if(NOT "" STREQUAL "" AND PACKAGE_FIND_VERSION_PRERELEASE STREQUAL "") 28 | message(AUTHOR_WARNING "To use this prerelease version of ${PACKAGE_FIND_NAME}, set ${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE to '' or greater.") 29 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 30 | endif() 31 | 32 | # Not robustly SemVer compliant, but protobuf never uses '.' separated prerelease identifiers. 33 | if(PACKAGE_FIND_VERSION_PRERELEASE STRGREATER "") 34 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 35 | endif() 36 | endif() 37 | endif() 38 | 39 | # Check and save build options used to create this package 40 | macro(_check_and_save_build_option OPTION VALUE) 41 | if(DEFINED ${PACKAGE_FIND_NAME}_${OPTION} AND 42 | NOT ${PACKAGE_FIND_NAME}_${OPTION} STREQUAL ${VALUE}) 43 | set(PACKAGE_VERSION_UNSUITABLE TRUE) 44 | endif() 45 | set(${PACKAGE_FIND_NAME}_${OPTION} ${VALUE} PARENT_SCOPE) 46 | endmacro() 47 | _check_and_save_build_option(WITH_ZLIB ON) 48 | _check_and_save_build_option(MSVC_STATIC_RUNTIME ON) 49 | _check_and_save_build_option(BUILD_SHARED_LIBS OFF) 50 | 51 | # if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: 52 | if(CMAKE_SIZEOF_VOID_P AND "8") 53 | # check that the installed version has the same 32/64bit-ness as the one which is currently searching: 54 | if(NOT CMAKE_SIZEOF_VOID_P EQUAL "8") 55 | math(EXPR installedBits "8 * 8") 56 | set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") 57 | set(PACKAGE_VERSION_UNSUITABLE TRUE) 58 | endif() 59 | endif() 60 | 61 | -------------------------------------------------------------------------------- /src/extra/cmake/R-4.4/protobuf-config-version.cmake: -------------------------------------------------------------------------------- 1 | set(PACKAGE_VERSION "29.3.0") 2 | set(${PACKAGE_FIND_NAME}_VERSION_PRERELEASE "" PARENT_SCOPE) 3 | 4 | # Prerelease versions cannot be passed in directly via the find_package command, 5 | # so we allow users to specify it in a variable 6 | if(NOT DEFINED "${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE") 7 | set("${${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE}" "") 8 | else() 9 | set(PACKAGE_FIND_VERSION ${PACKAGE_FIND_VERSION}-${${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE}) 10 | endif() 11 | set(PACKAGE_FIND_VERSION_PRERELEASE "${${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE}") 12 | 13 | # VERSION_EQUAL ignores the prerelease strings, so we use STREQUAL. 14 | if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) 15 | set(PACKAGE_VERSION_EXACT TRUE) 16 | endif() 17 | 18 | set(PACKAGE_VERSION_COMPATIBLE TRUE) #Assume true until shown otherwise 19 | 20 | if(PACKAGE_FIND_VERSION) #Only perform version checks if one is given 21 | if(NOT PACKAGE_FIND_VERSION_MAJOR EQUAL "5") 22 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 23 | elseif(PACKAGE_FIND_VERSION VERSION_GREATER PACKAGE_VERSION) 24 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 25 | elseif(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) 26 | # Do not match prerelease versions to non-prerelease version requests. 27 | if(NOT "" STREQUAL "" AND PACKAGE_FIND_VERSION_PRERELEASE STREQUAL "") 28 | message(AUTHOR_WARNING "To use this prerelease version of ${PACKAGE_FIND_NAME}, set ${PACKAGE_FIND_NAME}_FIND_VERSION_PRERELEASE to '' or greater.") 29 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 30 | endif() 31 | 32 | # Not robustly SemVer compliant, but protobuf never uses '.' separated prerelease identifiers. 33 | if(PACKAGE_FIND_VERSION_PRERELEASE STRGREATER "") 34 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 35 | endif() 36 | endif() 37 | endif() 38 | 39 | # Check and save build options used to create this package 40 | macro(_check_and_save_build_option OPTION VALUE) 41 | if(DEFINED ${PACKAGE_FIND_NAME}_${OPTION} AND 42 | NOT ${PACKAGE_FIND_NAME}_${OPTION} STREQUAL ${VALUE}) 43 | set(PACKAGE_VERSION_UNSUITABLE TRUE) 44 | endif() 45 | set(${PACKAGE_FIND_NAME}_${OPTION} ${VALUE} PARENT_SCOPE) 46 | endmacro() 47 | 48 | if(PACKAGE_VERSION_COMPATIBLE) 49 | _check_and_save_build_option(WITH_ZLIB ON) 50 | _check_and_save_build_option(MSVC_STATIC_RUNTIME ON) 51 | _check_and_save_build_option(BUILD_SHARED_LIBS OFF) 52 | endif() 53 | 54 | # if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: 55 | if(CMAKE_SIZEOF_VOID_P AND "8") 56 | # check that the installed version has the same 32/64bit-ness as the one which is currently searching: 57 | if(NOT CMAKE_SIZEOF_VOID_P EQUAL "8") 58 | math(EXPR installedBits "8 * 8") 59 | set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") 60 | set(PACKAGE_VERSION_UNSUITABLE TRUE) 61 | endif() 62 | endif() 63 | 64 | -------------------------------------------------------------------------------- /R/meter-provider-memory.R: -------------------------------------------------------------------------------- 1 | meter_provider_memory_new <- function(opts = NULL) { 2 | opts <- as_meter_provider_memory_options(opts) 3 | self <- new_object( 4 | c("otel_meter_provider_memory", "otel_meter_provider"), 5 | get_meter = function( 6 | name = NULL, 7 | version = NULL, 8 | schema_url = NULL, 9 | attributes = NULL, 10 | ... 11 | ) { 12 | meter_new(self, name, version, schema_url, attributes, ...) 13 | }, 14 | flush = function() { 15 | invisible(ccall(otel_meter_provider_flush, self$xptr, NULL)) 16 | }, 17 | shutdown = function() { 18 | ccall(otel_meter_provider_shutdown, self$xptr, NULL) 19 | invisible(self) 20 | }, 21 | get_metrics = function() { 22 | ccall(otel_meter_provider_memory_get_metrics, self$xptr) 23 | } 24 | ) 25 | 26 | attributes <- as_otel_attributes(the$default_resource_attributes) 27 | self$xptr <- ccall(otel_create_meter_provider_memory, opts, attributes) 28 | self 29 | } 30 | 31 | meter_provider_memory_options <- function() { 32 | as_meter_provider_memory_options(NULL) 33 | } 34 | 35 | #' In-memory meter provider for testing 36 | #' 37 | #' @description 38 | #' Collects metrics measurements in memory. This is useful for testing your 39 | #' instrumented R package or application. 40 | #' 41 | #' [with_otel_record()] uses this meter provider. 42 | #' Use [with_otel_record()] in your tests to record telemetry and check 43 | #' that it is correct. 44 | #' 45 | #' # Usage 46 | #' 47 | #' ``` 48 | #' mp <- meter_provider_memory$new(opts = NULL) 49 | #' mp$get_metrics() 50 | #' meter_provider_memory$options() 51 | #' ``` 52 | #' 53 | #' `mp$get_metrics()` erases the internal buffer of the meter provider. 54 | #' 55 | #' # Arguments 56 | #' 57 | #' - `opts`: Named list of options. See below. 58 | #' 59 | #' # Options 60 | #' 61 | #' ## Memory exporter options 62 | #' 63 | #' ```{r} 64 | #' #| echo: FALSE 65 | #' #| results: asis 66 | #' cat(doc_memory_exporter_options(meter_provider_memory_options_evs())) 67 | #' ``` 68 | #' 69 | #' ## Metric reader options 70 | #' 71 | #' ```{r} 72 | #' #| echo: FALSE 73 | #' #| results: asis 74 | #' cat(doc_metric_reader_options()) 75 | #' ``` 76 | #' 77 | #' ## Metric exporter options 78 | #' 79 | #' ```{r} 80 | #' #| echo: FALSE 81 | #' #| results: asis 82 | #' cat(doc_metric_exporter_options()) 83 | #' ``` 84 | #' 85 | #' @return 86 | #' `meter_provider_memory$new()` returns an [otel::otel_meter_provider] 87 | #' object. `mp$get_metrics()` returns a named list of recorded metrics. 88 | #' 89 | #' `meter_provider_memory$options()` returns a named list, the current 90 | #' values for all options. 91 | #' 92 | #' @usage NULL 93 | #' @format NULL 94 | #' @export 95 | #' @examples 96 | #' meter_provider_memory$options() 97 | 98 | meter_provider_memory <- list( 99 | new = meter_provider_memory_new, 100 | options = meter_provider_memory_options 101 | ) 102 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/collector-cc.md: -------------------------------------------------------------------------------- 1 | # otel_decode_log_record_ 2 | 3 | Code 4 | lrec 5 | Output 6 | $severity_text 7 | [1] "INFO" 8 | 9 | $trace_id 10 | [1] "" 11 | 12 | $span_id 13 | [1] "" 14 | 15 | $has_body 16 | [1] TRUE 17 | 18 | $body 19 | [1] "Test!" 20 | 21 | $attributes 22 | NULL 23 | 24 | $event_name 25 | [1] "" 26 | 27 | $dropped_attributes_count 28 | [1] 0 29 | 30 | 31 | --- 32 | 33 | Code 34 | ccall(otel_parse_log_record, logmsg) 35 | Condition 36 | Error: 37 | ! Failed to parse Protobuf log message @r-collector.c:14 (otel_parse_log_record) 38 | 39 | # otel_decode_metrics_record_ 40 | 41 | Code 42 | mets 43 | Output 44 | [[1]] 45 | [[1]]$schema_url 46 | [1] "" 47 | 48 | [[1]]$scope_metrics 49 | [[1]]$scope_metrics[[1]] 50 | [[1]]$scope_metrics[[1]]$schema_url 51 | [1] "" 52 | 53 | [[1]]$scope_metrics[[1]]$metrics 54 | [[1]]$scope_metrics[[1]]$metrics[[1]] 55 | [[1]]$scope_metrics[[1]]$metrics[[1]]$name 56 | [1] "ctr" 57 | 58 | [[1]]$scope_metrics[[1]]$metrics[[1]]$description 59 | [1] "" 60 | 61 | [[1]]$scope_metrics[[1]]$metrics[[1]]$unit 62 | [1] "" 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | --- 71 | 72 | Code 73 | ccall(otel_parse_metrics_record, metmsg) 74 | Condition 75 | Error: 76 | ! Failed to parse Protobuf metrics message @r-collector.c:27 (otel_parse_metrics_record) 77 | 78 | # otel_encode_response_ 79 | 80 | Code 81 | encode_response("traces") 82 | Output 83 | raw(0) 84 | 85 | --- 86 | 87 | Code 88 | encode_response("traces", "partial-success", error_message = "partial fail!", 89 | rejected = 1L) 90 | Output 91 | [1] 0a 11 08 01 12 0d 70 61 72 74 69 61 6c 20 66 61 69 6c 21 92 | 93 | --- 94 | 95 | Code 96 | encode_response("traces", "failure", error_message = "fail!", error_code = 2L) 97 | Output 98 | [1] 08 02 12 05 66 61 69 6c 21 99 | 100 | --- 101 | 102 | Code 103 | encode_response("metrics") 104 | Output 105 | raw(0) 106 | 107 | --- 108 | 109 | Code 110 | encode_response("metrics", "partial-success", error_message = "partial fail!", 111 | rejected = 1L) 112 | Output 113 | [1] 0a 11 08 01 12 0d 70 61 72 74 69 61 6c 20 66 61 69 6c 21 114 | 115 | --- 116 | 117 | Code 118 | encode_response("logs") 119 | Output 120 | raw(0) 121 | 122 | --- 123 | 124 | Code 125 | encode_response("logs", "partial-success", error_message = "partial fail!", 126 | rejected = 1L) 127 | Output 128 | [1] 0a 11 08 01 12 0d 70 61 72 74 69 61 6c 20 66 61 69 6c 21 129 | 130 | -------------------------------------------------------------------------------- /tests/testthat/test-meter.R: -------------------------------------------------------------------------------- 1 | test_that("meter, counter", { 2 | mp <- meter_provider_memory_new() 3 | on.exit(mp$shutdown(), add = TRUE) 4 | mtr <- mp$get_meter() 5 | expect_true(mtr$is_enabled()) 6 | ctr <- mtr$create_counter("ctr") 7 | ctr$add(1) 8 | ctr$add(10) 9 | mp$flush() 10 | mp$shutdown() 11 | mrcs <- mp$get_metrics() 12 | md <- mrcs[[2]][["scope_metric_data"]][[1]][["metric_data"]][[1]] 13 | expect_equal(md[["instrument_name"]], "ctr") 14 | expect_equal(md[["instrument_type"]], "counter") 15 | expect_equal(md[["instrument_value_type"]], "double") 16 | expect_equal(md[["point_data_attr"]][[1]][["value"]][["value"]], 11) 17 | }) 18 | 19 | test_that("meter, up_down_counter", { 20 | mp <- meter_provider_memory_new() 21 | on.exit(mp$shutdown(), add = TRUE) 22 | mtr <- mp$get_meter() 23 | udc <- mtr$create_up_down_counter("udctr") 24 | udc$add(1) 25 | udc$add(10) 26 | mp$flush() 27 | mp$shutdown() 28 | mrcs <- mp$get_metrics() 29 | md <- mrcs[[2]][["scope_metric_data"]][[1]][["metric_data"]][[1]] 30 | expect_equal(md[["instrument_name"]], "udctr") 31 | expect_equal(md[["instrument_type"]], "up_down_counter") 32 | expect_equal(md[["instrument_value_type"]], "double") 33 | expect_equal(md[["point_data_attr"]][[1]][["value"]][["value"]], 11) 34 | }) 35 | 36 | test_that("meter, histogram", { 37 | skip("unreliable") 38 | mp <- meter_provider_memory_new() 39 | on.exit(mp$shutdown(), add = TRUE) 40 | mtr <- mp$get_meter() 41 | hst <- mtr$create_histogram("hst") 42 | hst$record(1) 43 | hst$record(10) 44 | mp$flush() 45 | mp$shutdown() 46 | mrcs <- mp$get_metrics() 47 | md <- mrcs[[2]][["scope_metric_data"]][[1]][["metric_data"]][[1]] 48 | expect_equal(md[["instrument_name"]], "hst") 49 | expect_equal(md[["instrument_type"]], "histogram") 50 | expect_equal(md[["instrument_value_type"]], "double") 51 | expect_equal(md[["point_data_attr"]][[1]][["value"]][["sum"]], 11) 52 | expect_equal(md[["point_data_attr"]][[1]][["value"]][["min"]], 1) 53 | expect_equal(md[["point_data_attr"]][[1]][["value"]][["max"]], 10) 54 | expect_equal(md[["point_data_attr"]][[1]][["value"]][["count"]], 2) 55 | }) 56 | 57 | test_that("meter, gauge", { 58 | skip("Unreliable") 59 | mp <- meter_provider_memory_new() 60 | on.exit(mp$shutdown(), add = TRUE) 61 | mtr <- mp$get_meter() 62 | udc <- mtr$create_gauge("gge") 63 | udc$record(1) 64 | udc$record(10) 65 | mp$flush() 66 | mp$shutdown() 67 | mrcs <- mp$get_metrics() 68 | md <- mrcs[[2]][["scope_metric_data"]][[1]][["metric_data"]][[1]] 69 | expect_equal(md[["instrument_name"]], "gge") 70 | expect_equal(md[["instrument_type"]], "gauge") 71 | expect_equal(md[["instrument_value_type"]], "double") 72 | expect_equal(md[["point_data_attr"]][[1]][["value"]][["value"]], 10) 73 | }) 74 | 75 | test_that("meter_new when metrics are disabled", { 76 | fake(meter_new, "find_instrumentation_scope", list(on = FALSE, name = "nm")) 77 | mtr <- meter_new("name") 78 | expect_s3_class(mtr, "otel_meter_noop") 79 | }) 80 | -------------------------------------------------------------------------------- /src/context.c: -------------------------------------------------------------------------------- 1 | #define R_USE_C99_IN_CXX 1 2 | #include 3 | #include 4 | 5 | // TODO: not yet on R 4.6.x, except if forced for CI testing 6 | #if R_VERSION < R_Version(3,5,0) || !defined(OTEL_BUILD_SAFE) 7 | 8 | SEXP otel_error_object(void) { 9 | SEXP ret = PROTECT(Rf_allocVector(VECSXP, 2)); 10 | SET_VECTOR_ELT(ret, 0, Rf_ScalarLogical(0)); 11 | SET_VECTOR_ELT(ret, 1, R_NilValue); 12 | UNPROTECT(1); 13 | return ret; 14 | } 15 | 16 | #else 17 | 18 | #ifdef WIN32 19 | #include 20 | typedef int sigset_t; 21 | typedef struct { 22 | jmp_buf jmpbuf; 23 | int mask_was_saved; 24 | sigset_t saved_mask; 25 | } sigjmp_buf[1]; 26 | #else 27 | #include 28 | #endif 29 | #define JMP_BUF sigjmp_buf 30 | 31 | struct R_bcstack_t; 32 | struct RPRSTACK; 33 | #if R_VERSION >= R_Version(4,4,0) 34 | typedef struct { 35 | int tag; 36 | int flags; 37 | union { 38 | int ival; 39 | double dval; 40 | SEXP sxpval; 41 | } u; 42 | } R_bcstack_t; 43 | #endif 44 | 45 | struct r_context { 46 | struct r_context *nextcontext; 47 | int callflag; 48 | JMP_BUF cjmpbuf; 49 | int cstacktop; 50 | int evaldepth; 51 | SEXP promargs; 52 | SEXP callfun; 53 | SEXP sysparent; 54 | SEXP call; 55 | SEXP cloenv; 56 | SEXP conexit; 57 | void (*cend)(void *); 58 | void *cenddata; 59 | void *vmax; 60 | int intsusp; 61 | int gcenabled; 62 | int bcintactive; 63 | SEXP bcbody; 64 | void* bcpc; 65 | #if R_VERSION >= R_Version(4,4,0) 66 | ptrdiff_t relpc; 67 | #endif 68 | SEXP handlerstack; 69 | SEXP restartstack; 70 | struct RPRSTACK *prstack; 71 | struct R_bcstack_t *nodestack; 72 | #if R_VERSION >= R_Version(4,0,0) 73 | struct R_bcstack_t *bcprottop; 74 | #endif 75 | SEXP srcref; 76 | int browserfinish; 77 | #if R_VERSION < R_Version(4,4,0) 78 | SEXP returnValue; 79 | #else 80 | R_bcstack_t returnValue; 81 | #endif 82 | struct r_context *jumptarget; 83 | int jumpmask; 84 | }; 85 | 86 | extern struct r_context *R_GlobalContext; 87 | SEXP otel_error_object(void) { 88 | SEXP ret = PROTECT(Rf_allocVector(VECSXP, 2)); 89 | SET_VECTOR_ELT(ret, 0, Rf_ScalarLogical(1)); 90 | SET_VECTOR_ELT(ret, 1, R_NilValue); 91 | 92 | struct r_context *c = R_GlobalContext; 93 | for (; c != NULL; c = c->nextcontext) { 94 | for (SEXP hs = c->handlerstack; hs != R_NilValue; hs = CDR(hs)) { 95 | if (TYPEOF(CAR(hs)) == VECSXP && Rf_length(CAR(hs)) >= 5) { 96 | SEXP hs5 = VECTOR_ELT(CAR(hs), 4); 97 | if (TYPEOF(hs5) == VECSXP && Rf_length(hs5) >= 1) { 98 | SEXP hs51 = VECTOR_ELT(hs5, 0); 99 | if (!Rf_isNull(hs51)) { 100 | SET_VECTOR_ELT(ret, 1, VECTOR_ELT(hs5, 0)); 101 | UNPROTECT(1); 102 | return ret; 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | UNPROTECT(1); 110 | return ret; 111 | } 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /src/otel_attributes.h: -------------------------------------------------------------------------------- 1 | #include "opentelemetry/nostd/shared_ptr.h" 2 | #include "opentelemetry/common/key_value_iterable.h" 3 | 4 | namespace nostd = opentelemetry::nostd; 5 | namespace common = opentelemetry::common; 6 | 7 | #include "otel_common.h" 8 | #include "otel_common_cpp.h" 9 | 10 | std::vector otel_string_array_to_vec( 11 | struct otel_string_array &s); 12 | 13 | class RKeyValueIterable : public common::KeyValueIterable { 14 | public: 15 | RKeyValueIterable(struct otel_attributes &attributes) 16 | : attributes_(attributes) { } 17 | 18 | virtual bool ForEachKeyValue( 19 | nostd::function_ref 20 | callback) const noexcept { 21 | for (size_t i = 0; i < attributes_.count; i++) { 22 | struct otel_attribute &attr = attributes_.a[i]; 23 | bool cont = true; 24 | switch (attr.type) { 25 | case k_string: 26 | cont = callback(attr.name, attr.val.string.s); 27 | break; 28 | case k_boolean: 29 | cont = callback(attr.name, (bool) attr.val.boolean); 30 | break; 31 | case k_double: 32 | cont = callback(attr.name, attr.val.dbl); 33 | break; 34 | case k_int64: 35 | cont = callback(attr.name, attr.val.int64); 36 | break; 37 | case k_string_array: { 38 | std::vector v = 39 | otel_string_array_to_vec(attr.val.string_array); 40 | nostd::span vv(v.data(), v.size()); 41 | cont = callback(attr.name, vv); 42 | } 43 | break; 44 | case k_boolean_array: { 45 | size_t c = attr.val.boolean_array.count; 46 | bool *v = new bool[c]; 47 | for (size_t i = 0; i < c; i++) { 48 | v[i] = attr.val.boolean_array.a[i]; 49 | } 50 | nostd::span vv(v, c); 51 | cont = callback(attr.name, vv); 52 | delete [] v; 53 | } 54 | break; 55 | case k_double_array: { 56 | nostd::span vv( 57 | attr.val.dbl_array.a, 58 | attr.val.dbl_array.count 59 | ); 60 | cont = callback(attr.name, vv); 61 | } 62 | break; 63 | case k_int64_array: { 64 | nostd::span vv( 65 | attr.val.int64_array.a, 66 | attr.val.int64_array.count 67 | ); 68 | cont = callback(attr.name, vv); 69 | } 70 | break; 71 | default: 72 | // noexcept, but this cannot happen, anyway. 73 | break; // # nocov 74 | } 75 | if (!cont) return(false); 76 | } 77 | return(true); 78 | } 79 | 80 | // TODO: is this needed? 81 | // # nocov start 82 | virtual size_t size() const noexcept { 83 | return attributes_.count; 84 | } 85 | // # nocov end 86 | 87 | private: 88 | struct otel_attributes &attributes_; 89 | }; 90 | -------------------------------------------------------------------------------- /R/tracer-provider-http.R: -------------------------------------------------------------------------------- 1 | tracer_provider_http_new <- function(opts = NULL) { 2 | opts <- as_tracer_provider_http_options(opts) 3 | self <- new_object( 4 | c("otel_tracer_provider_http", "otel_tracer_provider"), 5 | get_tracer = function( 6 | name = NULL, 7 | version = NULL, 8 | schema_url = NULL, 9 | attributes = NULL, 10 | ... 11 | ) { 12 | tracer_new(self, name, version, schema_url, attributes, ...) 13 | }, 14 | flush = function() { 15 | ccall(otel_tracer_provider_flush, self$xptr) 16 | } 17 | ) 18 | 19 | attributes <- as_otel_attributes(the$default_resource_attributes) 20 | self$xptr <- ccall(otel_create_tracer_provider_http, opts, attributes) 21 | self 22 | } 23 | 24 | tracer_provider_http_options <- function() { 25 | ropts <- as_tracer_provider_http_options(NULL) 26 | copts <- ccall(otel_tracer_provider_http_options) 27 | # override the ones that are in the spec 28 | spec <- c( 29 | "url", 30 | "content_type", 31 | "timeout", 32 | "http_headers", 33 | "ssl_ca_cert_path", 34 | "ssl_ca_cert_string", 35 | "ssl_client_key_path", 36 | "ssl_client_key_string", 37 | "ssl_client_cert_path", 38 | "ssl_client_cert_string", 39 | "compression" 40 | ) 41 | ropts[spec] <- copts[spec] 42 | ropts[["content_type"]] <- as_otlp_content_type(ropts[["content_type"]]) 43 | 44 | # batch processor options are handled by CPP and are in the spec 45 | bspopts <- ccall(otel_bsp_defaults) 46 | ropts <- utils::modifyList(ropts, bspopts) 47 | 48 | ropts 49 | } 50 | 51 | #' Tracer provider to export traces over HTTP 52 | #' 53 | #' @description 54 | #' This is the OTLP HTTP exporter. 55 | #' 56 | #' Select this tracer provider with `OTEL_TRACES_EXPORTER=otlp`. 57 | #' 58 | #' # Usage 59 | #' 60 | #' Externally: 61 | #' ``` 62 | #' OTEL_TRACES_EXPORTER=otlp 63 | #' ``` 64 | #' 65 | #' From R: 66 | #' ``` 67 | #' tracer_provider_http$new(opts = NULL) 68 | #' tracer_provider_http$options() 69 | #' ``` 70 | #' 71 | #' # Arguments 72 | #' 73 | #' - `opts`: Named list of options. See below. 74 | #' 75 | #' # Options 76 | #' 77 | #' ## HTTP exporter options 78 | #' 79 | #' ```{r} 80 | #' #| echo: FALSE 81 | #' #| results: asis 82 | #' cat(doc_http_exporter_options( 83 | #' "traces", 84 | #' tracer_provider_http_options_evs(), 85 | #' tracer_provider_http$options() 86 | #' )) 87 | #' ``` 88 | #' 89 | #' ## Batch processor options 90 | #' 91 | #' ```{r} 92 | #' #| echo: FALSE 93 | #' #| results: asis 94 | #' cat(doc_batch_processor_options(tracer_provider_http$options())) 95 | #' ``` 96 | #' 97 | #' @return 98 | #' `tracer_provider_http$new()` returns an [otel::otel_tracer_provider] 99 | #' object. 100 | #' 101 | #' `tracer_provider_http$options()` returns a named list, the current 102 | #' values for all options. 103 | #' 104 | #' @format NULL 105 | #' @usage NULL 106 | #' @export 107 | #' @examples 108 | #' tracer_provider_http$options() 109 | 110 | tracer_provider_http <- list( 111 | new = tracer_provider_http_new, 112 | options = tracer_provider_http_options 113 | ) 114 | -------------------------------------------------------------------------------- /R/collector.R: -------------------------------------------------------------------------------- 1 | parse_log_record_message <- function(msg) { 2 | ccall(otel_parse_log_record, msg) 3 | } 4 | 5 | collector_app <- function() { 6 | app <- webfakes::new_app() 7 | app$locals$logs <- list() 8 | app$locals$traces <- list() 9 | app$locals$metrics <- list() 10 | app$post( 11 | c("/v1/traces", "/v1/metrics", "/v1/logs"), 12 | function(req, res) { 13 | if (req$get_header("content-type") != "application/x-protobuf") { 14 | bd <- encode_response( 15 | "traces", 16 | "failure", 17 | error_message = "missing or wrong content-type header" 18 | ) 19 | res$set_status(400L) 20 | res$send(bd) 21 | return() 22 | } 23 | "next" 24 | } 25 | ) 26 | app$post("/v1/traces", function(req, res) { 27 | # TODO 28 | }) 29 | app$post("/v1/metrics", function(req, res) { 30 | req <<- req 31 | record <- ccall(otel_parse_metrics_record, req$.body) 32 | app$locals$metrics <- c(app$locals$metrics, list(record)) 33 | bd <- encode_response("metrics") 34 | res$set_status(200) 35 | res$set_type("application/x-protobuf") 36 | res$send(bd) 37 | }) 38 | 39 | app$post("/v1/logs", function(req, res) { 40 | record <- ccall(otel_parse_log_record, req$.body) 41 | app$locals$logs <- c(app$locals$logs, list(record)) 42 | bd <- encode_response("logs") 43 | res$set_status(200) 44 | res$set_type("application/x-protobuf") 45 | res$send(bd) 46 | }) 47 | 48 | app$get("/logs", function(req, res) { 49 | if (length(app$locals$logs) == 0) { 50 | res$set_status(404) 51 | res$send("No logs available") 52 | return() 53 | } 54 | res$set_status(200) 55 | res$send_json(app$locals$logs, auto_unbox = TRUE) 56 | app$locals$logs <- list() 57 | }) 58 | 59 | app$get("/metrics", function(req, res) { 60 | if (length(app$locals$metrics) == 0) { 61 | res$set_status(404) 62 | res$send("No metrics available") 63 | return() 64 | } 65 | res$set_status(200) 66 | res$send_json(app$locals$metrics, auto_unbox = TRUE) 67 | app$locals$metrics <- list() 68 | }) 69 | 70 | app$get("/traces", function(req, res) { 71 | # TODO 72 | }) 73 | app 74 | } 75 | 76 | as_otlp_signal <- function(x) { 77 | choices <- c("traces", "metrics", "logs") 78 | x <- as_choice(x, choices, null = FALSE) 79 | x 80 | } 81 | 82 | as_otlp_result <- function(x) { 83 | choices <- c("success", "partial-success", "failure") 84 | x <- as_choice(x, choices, null = FALSE) 85 | x 86 | } 87 | 88 | encode_response <- function( 89 | signal, 90 | result = "success", 91 | error_message = NULL, 92 | rejected = 0L, 93 | error_code = 0L 94 | ) { 95 | signal <- as_otlp_signal(signal) 96 | result <- as_otlp_result(result) 97 | error_message <- as_string(error_message) 98 | rejected <- as_count(rejected) 99 | error_code <- as_count(error_code) 100 | ccall( 101 | otel_encode_response, 102 | signal, 103 | result, 104 | error_message, 105 | rejected, 106 | error_code 107 | ) 108 | } 109 | -------------------------------------------------------------------------------- /man/meter_provider_memory.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/meter-provider-memory.R 3 | \docType{data} 4 | \name{meter_provider_memory} 5 | \alias{meter_provider_memory} 6 | \title{In-memory meter provider for testing} 7 | \value{ 8 | \code{meter_provider_memory$new()} returns an \link[otel:otel_meter_provider]{otel::otel_meter_provider} 9 | object. \code{mp$get_metrics()} returns a named list of recorded metrics. 10 | 11 | \code{meter_provider_memory$options()} returns a named list, the current 12 | values for all options. 13 | } 14 | \description{ 15 | Collects metrics measurements in memory. This is useful for testing your 16 | instrumented R package or application. 17 | 18 | \code{\link[=with_otel_record]{with_otel_record()}} uses this meter provider. 19 | Use \code{\link[=with_otel_record]{with_otel_record()}} in your tests to record telemetry and check 20 | that it is correct. 21 | } 22 | \section{Usage}{ 23 | \if{html}{\out{
}}\preformatted{mp <- meter_provider_memory$new(opts = NULL) 24 | mp$get_metrics() 25 | meter_provider_memory$options() 26 | }\if{html}{\out{
}} 27 | 28 | \code{mp$get_metrics()} erases the internal buffer of the meter provider. 29 | } 30 | 31 | \section{Arguments}{ 32 | \itemize{ 33 | \item \code{opts}: Named list of options. See below. 34 | } 35 | } 36 | 37 | \section{Options}{ 38 | \subsection{Memory exporter options}{ 39 | \itemize{ 40 | \item \code{buffer_size}: buffer size, this is the maximum number of spans or 41 | metrics measurements that the provider can record. 42 | Must be positive. Value is set from 43 | \itemize{ 44 | \item the \code{opts} argument, or 45 | \item the \code{OTEL_R_EXPORTER_MEMORY_METRICS_BUFFER_SIZE} environment variable, or 46 | \item the \code{OTEL_R_EXPORTER_MEMORY_BUFFER_SIZE} environment variable, or 47 | \item the default is \code{100}. 48 | } 49 | } 50 | } 51 | 52 | \subsection{Metric reader options}{ 53 | \itemize{ 54 | \item \code{export_interval}: the time interval between the 55 | start of two export attempts, in milliseconds. Value is set from 56 | \itemize{ 57 | \item the \code{opts} argument, or 58 | \item the \code{OTEL_METRIC_EXPORT_INTERVAL} environment variable, or 59 | \item the default is \code{60000}. 60 | } 61 | \item \code{export_timeout}: Maximum allowed time to export data, in 62 | milliseconds. Value is set from 63 | \itemize{ 64 | \item the \code{opts} argument, or 65 | \item the \code{OTEL_METRIC_EXPORT_TIMEOUT} environment variable, or 66 | \item the default is \code{30000}. 67 | } 68 | } 69 | } 70 | 71 | \subsection{Metric exporter options}{ 72 | \itemize{ 73 | \item \code{aggregation_temporality}: possible values: 74 | \code{"unspecified"}, \code{"delta"}, \code{"cumulative"}, \code{"lowmemory"}. See the \href{https://opentelemetry.io/docs/specs/otel/metrics/data-model/#temporality}{OpenTelemetry data model}. 75 | Value is set from 76 | \itemize{ 77 | \item the \code{opts} argument, or 78 | \item the \code{OTEL_R_EXPORTER_OTLP_AGGREGATION_TEMPORALITY} environment variable, or 79 | \item the default is \code{"cumulative"}. 80 | } 81 | } 82 | } 83 | } 84 | 85 | \examples{ 86 | meter_provider_memory$options() 87 | } 88 | \keyword{datasets} 89 | -------------------------------------------------------------------------------- /R/logger-provider-http.R: -------------------------------------------------------------------------------- 1 | logger_provider_http_new <- function(opts = NULL) { 2 | opts <- as_logger_provider_http_options(opts) 3 | self <- new_object( 4 | c("otel_logger_provider_http", "otel_logger_provider"), 5 | get_logger = function( 6 | name = NULL, 7 | minimum_severity = NULL, 8 | version = NULL, 9 | schema_url = NULL, 10 | attributes = NULL, 11 | ... 12 | ) { 13 | logger_new( 14 | self, 15 | name, 16 | minimum_severity, 17 | version, 18 | schema_url, 19 | attributes, 20 | ... 21 | ) 22 | }, 23 | flush = function() { 24 | # noop currenrly 25 | } 26 | ) 27 | 28 | attributes <- as_otel_attributes(the$default_resource_attributes) 29 | self$xptr <- ccall(otel_create_logger_provider_http, opts, attributes) 30 | self 31 | } 32 | 33 | logger_provider_http_options <- function() { 34 | ropts <- as_logger_provider_http_options(NULL) 35 | copts <- ccall(otel_logger_provider_http_options) 36 | # override the ones that are in the spec 37 | spec <- c( 38 | "url", 39 | "content_type", 40 | "timeout", 41 | "http_headers", 42 | "ssl_ca_cert_path", 43 | "ssl_ca_cert_string", 44 | "ssl_client_key_path", 45 | "ssl_client_key_string", 46 | "ssl_client_cert_path", 47 | "ssl_client_cert_string", 48 | "compression" 49 | ) 50 | ropts[spec] <- copts[spec] 51 | ropts[["content_type"]] <- as_otlp_content_type(ropts[["content_type"]]) 52 | 53 | # batch processor options are handled by CPP and are in the spec 54 | blpropts <- ccall(otel_blrp_defaults) 55 | ropts <- utils::modifyList(ropts, blpropts) 56 | 57 | ropts 58 | } 59 | 60 | #' Logger provider to log over HTTP 61 | #' 62 | #' @description 63 | #' This is the OTLP HTTP exporter. 64 | #' 65 | #' # Usage 66 | #' 67 | #' Externally: 68 | #' ``` 69 | #' OTEL_LOGS_EXPORTER=otlp 70 | #' ``` 71 | #' 72 | #' From R: 73 | #' ``` 74 | #' logger_provider_http$new(opts = NULL) 75 | #' logger_provider_http$options() 76 | #' ``` 77 | #' 78 | #' # Arguments 79 | #' 80 | #' - `opts`: Named list of options. See below. 81 | #' 82 | #' # Options 83 | #' 84 | #' ## HTTP exporter options 85 | #' 86 | #' ```{r} 87 | #' #| echo: FALSE 88 | #' #| results: asis 89 | #' cat(doc_http_exporter_options( 90 | #' "logs", 91 | #' logger_provider_http_options_evs(), 92 | #' logger_provider_http$options() 93 | #' )) 94 | #' ``` 95 | #' 96 | #' ## Batch processor options 97 | #' 98 | #' ```{r} 99 | #' #| echo: FALSE 100 | #' #| results: asis 101 | #' cat(doc_batch_processor_options(logger_provider_http$options())) 102 | #' ``` 103 | #' 104 | #' @return 105 | #' `logger_provider_http$new()` returns an [otel::otel_logger_provider] 106 | #' object. 107 | #' 108 | #' `logger_provider_http$options()` returns a named list, the current 109 | #' values for all options. 110 | #' 111 | #' @format NULL 112 | #' @usage NULL 113 | #' @export 114 | #' @examples 115 | #' logger_provider_http$options() 116 | 117 | logger_provider_http <- list( 118 | new = logger_provider_http_new, 119 | options = logger_provider_http_options 120 | ) 121 | -------------------------------------------------------------------------------- /R/meter-provider-http.R: -------------------------------------------------------------------------------- 1 | meter_provider_http_new <- function(opts = NULL) { 2 | opts <- as_meter_provider_http_options(opts) 3 | 4 | self <- new_object( 5 | c("otel_meter_provider_http", "otel_meter_provider"), 6 | get_meter = function( 7 | name = NULL, 8 | version = NULL, 9 | schema_url = NULL, 10 | attributes = NULL, 11 | ... 12 | ) { 13 | meter_new(self, name, version, schema_url, attributes, ...) 14 | }, 15 | flush = function() { 16 | invisible(ccall(otel_meter_provider_flush, self$xptr, NULL)) 17 | }, 18 | shutdown = function() { 19 | ccall(otel_meter_provider_shutdown, self$xptr, NULL) 20 | invisible(self) 21 | } 22 | ) 23 | 24 | attributes <- as_otel_attributes(the$default_resource_attributes) 25 | self$xptr <- ccall(otel_create_meter_provider_http, opts, attributes) 26 | self 27 | } 28 | 29 | meter_provider_http_options <- function() { 30 | ropts <- as_meter_provider_http_options(NULL) 31 | copts <- ccall(otel_meter_provider_http_options) 32 | # override the ones that are in the spec 33 | spec <- c( 34 | "url", 35 | "content_type", 36 | "timeout", 37 | "http_headers", 38 | "ssl_ca_cert_path", 39 | "ssl_ca_cert_string", 40 | "ssl_client_key_path", 41 | "ssl_client_key_string", 42 | "ssl_client_cert_path", 43 | "ssl_client_cert_string", 44 | "compression" 45 | ) 46 | ropts[spec] <- copts[spec] 47 | ropts[["content_type"]] <- as_otlp_content_type(ropts[["content_type"]]) 48 | ropts 49 | } 50 | 51 | #' Meter provider to send collected metrics over HTTP 52 | #' 53 | #' @description 54 | #' This is the OTLP HTTP exporter. 55 | #' 56 | #' Select this tracer provider with `OTEL_METRICS_EXPORTER=otlp`. 57 | #' 58 | #' # Usage 59 | #' 60 | #' Externally: 61 | #' ``` 62 | #' OTEL_METRICS_EXPORTER=otlp 63 | #' ``` 64 | #' 65 | #' From R: 66 | #' ``` 67 | #' meter_provider_http$new(opts = NULL) 68 | #' meter_provider_http$options() 69 | #' ``` 70 | #' 71 | #' # Arguments 72 | #' 73 | #' - `opts`: Named list of options. See below. 74 | #' 75 | #' # Options 76 | #' 77 | #' ## HTTP exporter options 78 | #' 79 | #' ```{r} 80 | #' #| echo: FALSE 81 | #' #| results: asis 82 | #' cat(doc_http_exporter_options( 83 | #' "metrics", 84 | #' meter_provider_http_options_evs(), 85 | #' meter_provider_http$options() 86 | #' )) 87 | #' ``` 88 | #' 89 | #' ## Metric reader options 90 | #' 91 | #' ```{r} 92 | #' #| echo: FALSE 93 | #' #| results: asis 94 | #' cat(doc_metric_reader_options()) 95 | #' ``` 96 | #' 97 | #' ## Metric exporter options 98 | #' 99 | #' ```{r} 100 | #' #| echo: FALSE 101 | #' #| results: asis 102 | #' cat(doc_metric_exporter_options()) 103 | #' ``` 104 | #' 105 | #' @return 106 | #' `meter_provider_http$new()` returns an [otel::otel_meter_provider] 107 | #' object. 108 | #' 109 | #' `meter_provider_http$options()` returns a named list, the current 110 | #' values for all options. 111 | #' 112 | #' @format NULL 113 | #' @usage NULL 114 | #' @export 115 | #' @examples 116 | #' meter_provider_http$options() 117 | 118 | meter_provider_http <- list( 119 | new = meter_provider_http_new, 120 | options = meter_provider_http_options 121 | ) 122 | -------------------------------------------------------------------------------- /.github/workflows/rhub.yaml: -------------------------------------------------------------------------------- 1 | # R-hub's generic GitHub Actions workflow file. It's canonical location is at 2 | # https://github.com/r-hub/actions/blob/v1/workflows/rhub.yaml 3 | # You can update this file to a newer version using the rhub2 package: 4 | # 5 | # rhub::rhub_setup() 6 | # 7 | # It is unlikely that you need to modify this file manually. 8 | 9 | name: R-hub 10 | run-name: "${{ github.event.inputs.id }}: ${{ github.event.inputs.name || format('Manually run by {0}', github.triggering_actor) }}" 11 | 12 | on: 13 | workflow_dispatch: 14 | inputs: 15 | config: 16 | description: 'A comma separated list of R-hub platforms to use.' 17 | type: string 18 | default: 'linux,windows,macos' 19 | name: 20 | description: 'Run name. You can leave this empty now.' 21 | type: string 22 | id: 23 | description: 'Unique ID. You can leave this empty now.' 24 | type: string 25 | 26 | jobs: 27 | 28 | setup: 29 | runs-on: ubuntu-latest 30 | outputs: 31 | containers: ${{ steps.rhub-setup.outputs.containers }} 32 | platforms: ${{ steps.rhub-setup.outputs.platforms }} 33 | 34 | steps: 35 | # NO NEED TO CHECKOUT HERE 36 | - uses: r-hub/actions/setup@v1 37 | with: 38 | config: ${{ github.event.inputs.config }} 39 | id: rhub-setup 40 | 41 | linux-containers: 42 | needs: setup 43 | if: ${{ needs.setup.outputs.containers != '[]' }} 44 | runs-on: ubuntu-latest 45 | name: ${{ matrix.config.label }} 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | config: ${{ fromJson(needs.setup.outputs.containers) }} 50 | container: 51 | image: ${{ matrix.config.container }} 52 | 53 | steps: 54 | - uses: r-hub/actions/checkout@v1 55 | - uses: r-hub/actions/platform-info@v1 56 | with: 57 | token: ${{ secrets.RHUB_TOKEN }} 58 | job-config: ${{ matrix.config.job-config }} 59 | - uses: r-hub/actions/setup-deps@v1 60 | with: 61 | token: ${{ secrets.RHUB_TOKEN }} 62 | job-config: ${{ matrix.config.job-config }} 63 | - uses: r-hub/actions/run-check@v1 64 | with: 65 | token: ${{ secrets.RHUB_TOKEN }} 66 | job-config: ${{ matrix.config.job-config }} 67 | 68 | other-platforms: 69 | needs: setup 70 | if: ${{ needs.setup.outputs.platforms != '[]' }} 71 | runs-on: ${{ matrix.config.os }} 72 | name: ${{ matrix.config.label }} 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | config: ${{ fromJson(needs.setup.outputs.platforms) }} 77 | 78 | steps: 79 | - uses: r-hub/actions/checkout@v1 80 | - uses: r-hub/actions/setup-r@v1 81 | with: 82 | job-config: ${{ matrix.config.job-config }} 83 | token: ${{ secrets.RHUB_TOKEN }} 84 | - uses: r-hub/actions/platform-info@v1 85 | with: 86 | token: ${{ secrets.RHUB_TOKEN }} 87 | job-config: ${{ matrix.config.job-config }} 88 | - uses: r-hub/actions/setup-deps@v1 89 | with: 90 | job-config: ${{ matrix.config.job-config }} 91 | token: ${{ secrets.RHUB_TOKEN }} 92 | - uses: r-hub/actions/run-check@v1 93 | with: 94 | job-config: ${{ matrix.config.job-config }} 95 | token: ${{ secrets.RHUB_TOKEN }} 96 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/span.md: -------------------------------------------------------------------------------- 1 | # format_exception 2 | 3 | Code 4 | format_exception(base_error()) 5 | Output 6 | $exception.message 7 | [1] "" 8 | 9 | $exception.stacktrace 10 | [1] "doTryCatch(return(expr), name, parentenv, handler)" 11 | 12 | $exception.type 13 | [1] "simpleError" "error" "condition" 14 | 15 | 16 | --- 17 | 18 | Code 19 | format_exception(cli_error()) 20 | Output 21 | $exception.message 22 | [1] "" 23 | [2] "Error:" 24 | [3] "! Something went wrong." 25 | [4] "x You did not do the right thing." 26 | [5] "i You did another thing instead." 27 | [6] "---" 28 | [7] "Backtrace:" 29 | [8] " x" 30 | [9] " 1. +-base::tryCatch(...)" 31 | [10] " 2. | \\-base (local) tryCatchList(expr, classes, parentenv, handlers)" 32 | [11] " 3. | \\-base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])" 33 | [12] " 4. | \\-base (local) doTryCatch(return(expr), name, parentenv, handler)" 34 | [13] " 5. \\-cli::cli_abort(...)" 35 | [14] " 6. \\-rlang::abort(...)" 36 | 37 | $exception.stacktrace 38 | [1] " x" 39 | [2] " 1. +-base::tryCatch(...)" 40 | [3] " 2. | \\-base (local) tryCatchList(expr, classes, parentenv, handlers)" 41 | [4] " 3. | \\-base (local) tryCatchOne(expr, names, parentenv, handlers[[1L]])" 42 | [5] " 4. | \\-base (local) doTryCatch(return(expr), name, parentenv, handler)" 43 | [6] " 5. \\-cli::cli_abort(...)" 44 | [7] " 6. \\-rlang::abort(...)" 45 | 46 | $exception.type 47 | [1] "rlang_error" "error" "condition" 48 | 49 | 50 | --- 51 | 52 | Code 53 | format_exception(processx_error()) 54 | Output 55 | $exception.message 56 | [1] "" 57 | [2] "Error in `processx::run(\"false\")`:" 58 | [3] "! System command 'false' failed" 59 | [4] "---" 60 | [5] "Exit status: 1" 61 | [6] "Stderr: " 62 | 63 | $exception.stacktrace 64 | [1] "processx::run(\"false\")" 65 | 66 | $exception.type 67 | [1] "system_command_status_error" "system_command_error" 68 | [3] "rlib_error_3_0" "rlib_error" 69 | [5] "error" "condition" 70 | 71 | 72 | --- 73 | 74 | Code 75 | format_exception(callr_error()) 76 | Output 77 | $exception.message 78 | [1] "" 79 | [2] "Error: " 80 | [3] "! in callr subprocess." 81 | [4] "Caused by error in `1 + \"\"`:" 82 | [5] "! non-numeric argument to binary operator" 83 | [6] "---" 84 | [7] "Subprocess backtrace:" 85 | [8] "1. base::.handleSimpleError(function (e) ..." 86 | [9] "2. global h(simpleError(msg, call))" 87 | 88 | $exception.stacktrace 89 | [1] "" 90 | 91 | $exception.type 92 | [1] "callr_status_error" "callr_error" "rlib_error_3_0" 93 | [4] "rlib_error" "error" "condition" 94 | 95 | 96 | # span_context 97 | 98 | Code 99 | ctx$get_trace_flags() 100 | Output 101 | $is_sampled 102 | [1] TRUE 103 | 104 | $is_random 105 | [1] FALSE 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/testthat/test-meter-c.R: -------------------------------------------------------------------------------- 1 | test_that("finalizers", { 2 | do <- function() { 3 | mp <- meter_provider_memory_new() 4 | mtr <- mp$get_meter() 5 | ctr <- mtr$create_counter("ctr") 6 | udctr <- mtr$create_up_down_counter("udctr") 7 | hst <- mtr$create_histogram("hst") 8 | gge <- mtr$create_gauge("gge") 9 | mp$shutdown() 10 | } 11 | do() 12 | gc() 13 | gc() 14 | expect_true(TRUE) 15 | }) 16 | 17 | test_that("otel_meter_provider_memory_get_metrics error", { 18 | expect_snapshot( 19 | ccall(otel_meter_provider_memory_get_metrics, 1:10) 20 | ) 21 | 22 | x <- ccall(create_empty_xptr) 23 | expect_snapshot( 24 | error = TRUE, 25 | ccall(otel_meter_provider_memory_get_metrics, x) 26 | ) 27 | }) 28 | 29 | test_that("otel_get_meter error", { 30 | x <- ccall(create_empty_xptr) 31 | expect_snapshot(error = TRUE, { 32 | ccall(otel_get_meter, 1L, "foo", NULL, NULL, NULL) 33 | ccall(otel_get_meter, x, "foo", NULL, NULL, NULL) 34 | }) 35 | }) 36 | 37 | test_that("otel_meter_provider_flush error", { 38 | x <- ccall(create_empty_xptr) 39 | expect_snapshot(error = TRUE, { 40 | ccall(otel_meter_provider_flush, 1L, NULL) 41 | ccall(otel_meter_provider_flush, x, NULL) 42 | }) 43 | }) 44 | 45 | test_that("otel_meter_provider_shutdown error", { 46 | x <- ccall(create_empty_xptr) 47 | expect_snapshot(error = TRUE, { 48 | ccall(otel_meter_provider_shutdown, 1L, NULL) 49 | ccall(otel_meter_provider_shutdown, x, NULL) 50 | }) 51 | }) 52 | 53 | test_that("otel_create_counter error", { 54 | x <- ccall(create_empty_xptr) 55 | expect_snapshot(error = TRUE, { 56 | ccall(otel_create_counter, 1L, NULL, NULL, NULL) 57 | ccall(otel_create_counter, x, NULL, NULL, NULL) 58 | }) 59 | }) 60 | 61 | test_that("otel_counter_add error", { 62 | x <- ccall(create_empty_xptr) 63 | expect_snapshot(error = TRUE, { 64 | ccall(otel_counter_add, 1L, NULL, NULL, NULL) 65 | ccall(otel_counter_add, x, NULL, NULL, NULL) 66 | }) 67 | }) 68 | 69 | test_that("otel_create_up_down_counter error", { 70 | x <- ccall(create_empty_xptr) 71 | expect_snapshot(error = TRUE, { 72 | ccall(otel_create_up_down_counter, 1L, NULL, NULL, NULL) 73 | ccall(otel_create_up_down_counter, x, NULL, NULL, NULL) 74 | }) 75 | }) 76 | 77 | test_that("otel_up_down_counter_add error", { 78 | x <- ccall(create_empty_xptr) 79 | expect_snapshot(error = TRUE, { 80 | ccall(otel_up_down_counter_add, 1L, NULL, NULL, NULL) 81 | ccall(otel_up_down_counter_add, x, NULL, NULL, NULL) 82 | }) 83 | }) 84 | 85 | test_that("otel_create_histogram error", { 86 | x <- ccall(create_empty_xptr) 87 | expect_snapshot(error = TRUE, { 88 | ccall(otel_create_histogram, 1L, NULL, NULL, NULL) 89 | ccall(otel_create_histogram, x, NULL, NULL, NULL) 90 | }) 91 | }) 92 | 93 | test_that("otel_histogram_record error", { 94 | x <- ccall(create_empty_xptr) 95 | expect_snapshot(error = TRUE, { 96 | ccall(otel_histogram_record, 1L, NULL, NULL, NULL) 97 | ccall(otel_histogram_record, x, NULL, NULL, NULL) 98 | }) 99 | }) 100 | 101 | test_that("otel_create_gauge error", { 102 | x <- ccall(create_empty_xptr) 103 | expect_snapshot(error = TRUE, { 104 | ccall(otel_create_gauge, 1L, NULL, NULL, NULL) 105 | ccall(otel_create_gauge, x, NULL, NULL, NULL) 106 | }) 107 | }) 108 | 109 | test_that("otel_gauge_record error", { 110 | x <- ccall(create_empty_xptr) 111 | expect_snapshot(error = TRUE, { 112 | ccall(otel_gauge_record, 1L, NULL, NULL, NULL) 113 | ccall(otel_gauge_record, x, NULL, NULL, NULL) 114 | }) 115 | }) 116 | -------------------------------------------------------------------------------- /tests/testthat/test-tracer.R: -------------------------------------------------------------------------------- 1 | test_that("tracer_new", { 2 | trc_prv <- tracer_provider_memory_new() 3 | expect_equal( 4 | class(trc_prv), 5 | c("otel_tracer_provider_memory", "otel_tracer_provider") 6 | ) 7 | trc <- trc_prv$get_tracer("mytracer") 8 | expect_equal(class(trc), "otel_tracer") 9 | }) 10 | 11 | test_that("start_local_active_span", { 12 | spns <- with_otel_record({ 13 | trc <- otel::get_tracer("mytracer") 14 | spn1 <- trc$start_local_active_span("spn1") 15 | expect_equal(class(spn1), "otel_span") 16 | spn2 <- trc$start_local_active_span("spn2") 17 | spn2$end() 18 | spn1$end() 19 | })[["traces"]] 20 | 21 | expect_equal(length(spns), 2) 22 | expect_equal(spns[[1]]$name, "spn2") 23 | expect_equal(spns[[2]]$name, "spn1") 24 | expect_match(spns[[1]]$trace_id, "^[0-9a-f]+$") 25 | expect_equal(spns[[1]]$trace_id, spns[[2]]$trace_id) 26 | expect_match(spns[[1]]$span_id, "^[0-9a-f]+$") 27 | expect_match(spns[[2]]$span_id, "^[0-9a-f]+$") 28 | expect_match(spns[[1]]$parent, "^[0-9a-f]+$") 29 | expect_equal(spns[[2]]$parent, "0000000000000000") 30 | expect_equal(spns[[1]]$parent, spns[[2]]$span_id) 31 | expect_true( 32 | Sys.time() - spns[[1]]$start_time < as.difftime(3, units = "secs") 33 | ) 34 | expect_true(spns[[1]]$duration < 3) 35 | expect_equal(spns[[1]]$kind, "internal") 36 | expect_equal(spns[[1]]$status, "unset") 37 | # expect_equal(spns[[1]]$resources$service.name, "unknown_service") 38 | # expect_equal(spns[[1]]$`instr-lib`, "mytracer") 39 | }) 40 | 41 | test_that("is_enabled", { 42 | trc_prv <- tracer_provider_stdstream_new() 43 | trc <- trc_prv$get_tracer("mytracer") 44 | expect_true(trc$is_enabled()) 45 | }) 46 | 47 | test_that("start_span", { 48 | spns <- with_otel_record({ 49 | trc <- otel::get_tracer("mytracer") 50 | sess <- trc$start_span("sess") 51 | sess$end() 52 | })[["traces"]] 53 | 54 | expect_equal(spns[["sess"]]$name, "sess") 55 | }) 56 | 57 | test_that("get_active_span_context", { 58 | spid <- NULL 59 | spns <- with_otel_record(function() { 60 | trc <- otel::get_tracer("mytracer") 61 | spn1 <- trc$start_local_active_span("spn1") 62 | ctx <- trc$get_active_span_context() 63 | spid <<- ctx$get_span_id() 64 | }) 65 | 66 | # a new span is active 67 | expect_equal(spid, spns[["traces"]][["spn1"]]$span_id) 68 | }) 69 | 70 | test_that("flush", { 71 | trc_prv <- tracer_provider_stdstream_new() 72 | trc <- trc_prv$get_tracer("mytracer") 73 | expect_silent(trc$flush()) 74 | }) 75 | 76 | test_that("extract_http_context", { 77 | hdrs <- NULL 78 | trid <- NULL 79 | spid <- NULL 80 | spns <- with_otel_record(function() { 81 | trc <- otel::get_tracer("mytracer") 82 | spn1 <- trc$start_local_active_span("spn1") 83 | ctx <- trc$get_active_span_context() 84 | hdrs <<- ctx$to_http_headers() 85 | trid <<- ctx$get_trace_id() 86 | spid <<- ctx$get_span_id() 87 | spn1$end() 88 | })[["traces"]] 89 | 90 | spns2 <- with_otel_record(function() { 91 | trc <- otel::get_tracer("mytracer2") 92 | ctx <- trc$extract_http_context(hdrs) 93 | spn2 <- trc$start_local_active_span("spn2", options = list(parent = ctx)) 94 | })[["traces"]] 95 | 96 | expect_equal(spns[["spn1"]][["trace_id"]], spns2[["spn2"]][["trace_id"]]) 97 | expect_equal(spns[["spn1"]][["span_id"]], spns2[["spn2"]][["parent"]]) 98 | }) 99 | 100 | test_that("tracer_new when tracing is disabled", { 101 | fake(tracer_new, "find_instrumentation_scope", list(on = FALSE, name = "nm")) 102 | lgr <- tracer_new("name") 103 | expect_s3_class(lgr, "otel_tracer_noop") 104 | }) 105 | -------------------------------------------------------------------------------- /tests/testthat/helper-mock.R: -------------------------------------------------------------------------------- 1 | fake <- local({ 2 | fake_through_tree <- function(tree, what, how) { 3 | for (d in tree) { 4 | for (parent in d) { 5 | parent_env <- parent[["parent_env"]] 6 | func_dict <- parent[["funcs"]] 7 | for (func_name in ls(func_dict, all.names = TRUE)) { 8 | func <- func_dict[[func_name]] 9 | func_env <- new.env(parent = environment(func)) 10 | 11 | what <- override_seperators(what, func_env) 12 | where_name <- override_seperators(func_name, parent_env) 13 | 14 | if (!is.function(how)) { 15 | assign(what, function(...) how, func_env) 16 | } else { 17 | assign(what, how, func_env) 18 | } 19 | 20 | environment(func) <- func_env 21 | locked <- exists(where_name, parent_env, inherits = FALSE) && 22 | bindingIsLocked(where_name, parent_env) 23 | if (locked) { 24 | baseenv()$unlockBinding(where_name, parent_env) 25 | } 26 | assign(where_name, func, parent_env) 27 | if (locked) { 28 | lockBinding(where_name, parent_env) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | override_seperators <- function(name, env) { 36 | mangled_name <- NULL 37 | for (sep in c("::", "$")) { 38 | if (grepl(sep, name, fixed = TRUE)) { 39 | elements <- strsplit(name, sep, fixed = TRUE) 40 | mangled_name <- paste( 41 | elements[[1L]][1L], 42 | elements[[1L]][2L], 43 | sep = "XXX" 44 | ) 45 | 46 | stub_list <- c(mangled_name) 47 | if ("stub_list" %in% names(attributes(get(sep, env)))) { 48 | stub_list <- c(stub_list, attributes(get(sep, env))[["stub_list"]]) 49 | } 50 | 51 | create_new_name <- create_create_new_name_function( 52 | stub_list, 53 | env, 54 | sep 55 | ) 56 | assign(sep, create_new_name, env) 57 | } 58 | } 59 | mangled_name %||% name 60 | } 61 | 62 | backtick <- function(x) { 63 | encodeString(x, quote = "`", na.encode = FALSE) 64 | } 65 | 66 | create_create_new_name_function <- function(stub_list, env, sep) { 67 | force(stub_list) 68 | force(env) 69 | force(sep) 70 | 71 | create_new_name <- function(pkg, func) { 72 | pkg_name <- deparse(substitute(pkg)) 73 | func_name <- deparse(substitute(func)) 74 | for (stub in stub_list) { 75 | if (paste(pkg_name, func_name, sep = "XXX") == stub) { 76 | return(eval(parse(text = backtick(stub)), env)) 77 | } 78 | } 79 | 80 | # used to avoid recursively calling the replacement function 81 | eval_env <- new.env(parent = parent.frame()) 82 | assign(sep, eval(parse(text = paste0("`", sep, "`"))), eval_env) 83 | 84 | code <- paste(pkg_name, backtick(func_name), sep = sep) 85 | return(eval(parse(text = code), eval_env)) 86 | } 87 | attributes(create_new_name) <- list(stub_list = stub_list) 88 | create_new_name 89 | } 90 | 91 | build_function_tree <- function(test_env, where, where_name) { 92 | func_dict <- new.env() 93 | func_dict[[where_name]] <- where 94 | tree <- list( 95 | list( 96 | list(parent_env = test_env, funcs = func_dict) 97 | ) 98 | ) 99 | 100 | tree 101 | } 102 | 103 | fake <- function(where, what, how) { 104 | where_name <- deparse(substitute(where)) 105 | stopifnot(is.character(what), length(what) == 1) 106 | test_env <- parent.frame() 107 | tree <- build_function_tree(test_env, where, where_name) 108 | fake_through_tree(tree, what, how) 109 | } 110 | }) 111 | -------------------------------------------------------------------------------- /tests/testthat/test-logger-sdk-cc.R: -------------------------------------------------------------------------------- 1 | test_that("otel_logger_provider_finally_ ++", { 2 | # otel_logger_finally_ 3 | # otel_create_logger_provider_file_ 4 | tmp <- tempfile() 5 | on.exit(unlink(tmp), add = TRUE) 6 | lp <- logger_provider_file$new(list(file_pattern = tmp)) 7 | lgr <- lp$get_logger("test") 8 | rm(lp, lgr) 9 | gc() 10 | gc() 11 | expect_true(TRUE) 12 | }) 13 | 14 | test_that("otel_logger_provider_file_options_defaults_", { 15 | expect_snapshot({ 16 | logger_provider_file$options() 17 | }) 18 | }) 19 | 20 | test_that("otel_logger_provider_flush_", { 21 | tmp <- tempfile() 22 | on.exit(unlink(tmp), add = TRUE) 23 | lp <- logger_provider_file$new(list(file_pattern = tmp)) 24 | lgr <- lp$get_logger("test") 25 | lgr$log("Hello there!") 26 | lp$flush() 27 | expect_true(file.size(tmp) > 0) 28 | }) 29 | 30 | test_that("otel_logger_provider_flush_ 2", { 31 | tmp <- tempfile() 32 | on.exit(unlink(tmp), add = TRUE) 33 | lp <- logger_provider_stdstream$new(list(output = tmp)) 34 | lgr <- lp$get_logger("test") 35 | lgr$log("Hello there!") 36 | lp$flush() 37 | expect_true(file.size(tmp) > 0) 38 | }) 39 | 40 | test_that("otel_get_minimum_log_severity_ ++", { 41 | # otel_set_minimum_log_severity 42 | # otel_get_logger_ 43 | # otel_logger_get_name_ 44 | # to_severity 45 | # otel_logger_is_enabled_ 46 | tmp <- tempfile() 47 | on.exit(unlink(tmp), add = TRUE) 48 | lp <- logger_provider_file$new(list(file_pattern = tmp)) 49 | lgr <- lp$get_logger("test") 50 | 51 | expect_equal(lgr$get_name(), "test") 52 | expect_true(lgr$is_enabled()) 53 | 54 | expect_equal(lgr$get_minimum_severity(), c(info = 9)) 55 | lgr$set_minimum_severity("debug") 56 | expect_equal(lgr$get_minimum_severity(), c(debug = 5)) 57 | 58 | for (lvl in otel::log_severity_levels) { 59 | lgr$set_minimum_severity(lvl) 60 | expect_equal(unname(lgr$get_minimum_severity()), lvl) 61 | } 62 | }) 63 | 64 | test_that("to_severity", { 65 | # otel_log_ 66 | tmp <- tempfile() 67 | on.exit(unlink(tmp), add = TRUE) 68 | lp <- logger_provider_file$new(list(file_pattern = tmp)) 69 | lgr <- lp$get_logger("test") 70 | lgr$set_minimum_severity(1) 71 | for (lvl in otel::log_severity_levels) { 72 | lgr$log("Hello, level {lvl}!", severity = lvl) 73 | } 74 | lp$flush() 75 | lns <- readLines(tmp) 76 | lvls <- map_int(lns, USE.NAMES = FALSE, function(ln) { 77 | psd <- jsonlite::fromJSON(ln, simplifyVector = FALSE) 78 | lrs <- psd$resourceLogs[[1]]$scopeLogs[[1]]$logRecords 79 | lrs[[1]]$severityNumber 80 | }) 81 | expect_equal(lvls, unname(otel::log_severity_levels)) 82 | }) 83 | 84 | test_that("otel_log_", { 85 | # hexchar 86 | tmp <- tempfile() 87 | on.exit(unlink(tmp), add = TRUE) 88 | lp <- logger_provider_file$new(list(file_pattern = tmp)) 89 | lgr <- lp$get_logger("test") 90 | trace_id <- strrep("a", nchar(otel::invalid_trace_id)) 91 | span_id <- strrep("b", nchar(otel::invalid_span_id)) 92 | ots <- structure(1754300727, class = c("POSIXct", "POSIXt")) 93 | lgr$log( 94 | "Hello, with trace id", 95 | trace_id = trace_id, 96 | span_id = span_id, 97 | observed_timestamp = ots 98 | ) 99 | lp$flush() 100 | lns <- readLines(tmp) 101 | psd <- jsonlite::fromJSON(lns, simplifyVector = FALSE) 102 | lrs <- psd$resourceLogs[[1]]$scopeLogs[[1]]$logRecords 103 | expect_equal(lrs[[1]]$traceId, trace_id) 104 | expect_equal(lrs[[1]]$spanId, span_id) 105 | # ns to s 106 | ots2 <- as.double( 107 | substr( 108 | lrs[[1]]$observedTimeUnixNano, 109 | 1, 110 | nchar(lrs[[1]]$observedTimeUnixNano) - 9 111 | ) 112 | ) 113 | expect_equal(ots2, as.double(ots)) 114 | }) 115 | 116 | test_that("otel_logger_provider_http_default_options_", { 117 | # otel_blrp_defaults_ 118 | expect_snapshot({ 119 | logger_provider_http$options() 120 | }) 121 | }) 122 | -------------------------------------------------------------------------------- /R/meter.R: -------------------------------------------------------------------------------- 1 | meter_new <- function( 2 | provider, 3 | name = NULL, 4 | version = NULL, 5 | schema_url = NULL, 6 | attributes = NULL, 7 | ... 8 | ) { 9 | name <- as_string(name, null = TRUE) 10 | inst_scope <- find_instrumentation_scope(name) 11 | name <- name %||% inst_scope[["name"]] 12 | if (!inst_scope[["on"]]) { 13 | return(otel::meter_provider_noop$new()$get_meter(name)) 14 | } 15 | 16 | self <- new_object( 17 | "otel_meter", 18 | create_counter = function( 19 | name, 20 | description = NULL, 21 | unit = NULL 22 | ) { 23 | counter_new(self, name, description, unit) 24 | }, 25 | create_up_down_counter = function( 26 | name, 27 | description = NULL, 28 | unit = NULL 29 | ) { 30 | up_down_counter_new(self, name, description, unit) 31 | }, 32 | create_histogram = function( 33 | name, 34 | description = NULL, 35 | unit = NULL 36 | ) { 37 | histogram_new(self, name, description, unit) 38 | }, 39 | create_gauge = function( 40 | name, 41 | description = NULL, 42 | unit = NULL 43 | ) { 44 | gauge_new(self, name, description, unit) 45 | }, 46 | is_enabled = function() { 47 | TRUE 48 | } 49 | ) 50 | self$provider <- provider 51 | self$name <- as_string(name) 52 | self$version <- as_string(version) 53 | self$schema_url <- as_string(schema_url) 54 | self$attributes <- as_otel_attributes(attributes) 55 | self$xptr <- ccall( 56 | otel_get_meter, 57 | self$provider$xptr, 58 | self$name, 59 | self$version, 60 | self$schema_url, 61 | self$attributes 62 | ) 63 | self 64 | } 65 | 66 | counter_new <- function(meter, name, description = NULL, unit = NULL) { 67 | self <- new_object( 68 | "otel_counter", 69 | add = function( 70 | value = 1L, 71 | attributes = NULL, 72 | span_context = NULL 73 | ) { 74 | # TODO: check args 75 | value <- as.double(value) 76 | ccall(otel_counter_add, self$xptr, value, attributes, span_context) 77 | } 78 | ) 79 | self$xptr <- ccall( 80 | otel_create_counter, 81 | meter$xptr, 82 | name, 83 | description, 84 | unit 85 | ) 86 | self 87 | } 88 | 89 | up_down_counter_new <- function( 90 | meter, 91 | name, 92 | description = NULL, 93 | unit = NULL 94 | ) { 95 | self <- new_object( 96 | "otel_up_down_counter", 97 | add = function( 98 | value = 1L, 99 | attributes = NULL, 100 | span_context = NULL 101 | ) { 102 | # TODO: check args 103 | value <- as.double(value) 104 | ccall( 105 | otel_up_down_counter_add, 106 | self$xptr, 107 | value, 108 | attributes, 109 | span_context 110 | ) 111 | } 112 | ) 113 | self$xptr <- ccall( 114 | otel_create_up_down_counter, 115 | meter$xptr, 116 | name, 117 | description, 118 | unit 119 | ) 120 | self 121 | } 122 | 123 | histogram_new <- function(meter, name, description = NULL, unit = NULL) { 124 | self <- new_object( 125 | "otel_histogram", 126 | record = function( 127 | value, 128 | attributes = NULL, 129 | span_context = NULL 130 | ) { 131 | # TODO: check args 132 | value <- as.double(value) 133 | ccall(otel_histogram_record, self$xptr, value, attributes, span_context) 134 | } 135 | ) 136 | self$xptr <- ccall( 137 | otel_create_histogram, 138 | meter$xptr, 139 | name, 140 | description, 141 | unit 142 | ) 143 | self 144 | } 145 | 146 | gauge_new <- function(meter, name, description = NULL, unit = NULL) { 147 | self <- new_object( 148 | "otel_gauge", 149 | record = function( 150 | value, 151 | attributes = NULL, 152 | span_context = NULL 153 | ) { 154 | # TODO: check args 155 | value <- as.double(value) 156 | ccall(otel_gauge_record, self$xptr, value, attributes, span_context) 157 | } 158 | ) 159 | self$xptr <- ccall( 160 | otel_create_gauge, 161 | meter$xptr, 162 | name, 163 | description, 164 | unit 165 | ) 166 | self 167 | } 168 | -------------------------------------------------------------------------------- /tests/testthat/test-collector-cc.R: -------------------------------------------------------------------------------- 1 | test_that("otel_decode_log_record_", { 2 | # fmt: skip 3 | logmsg <- as.raw(c( 4 | 0x0a, 0xcb, 0x01, 0x0a, 0x8e, 0x01, 0x0a, 0x21, 0x0a, 0x15, 0x74, 5 | 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x73, 0x64, 6 | 0x6b, 0x2e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x08, 7 | 0x0a, 0x06, 0x31, 0x2e, 0x32, 0x31, 0x2e, 0x30, 0x0a, 0x25, 0x0a, 8 | 0x12, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 9 | 0x73, 0x64, 0x6b, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0f, 0x0a, 10 | 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 11 | 0x74, 0x72, 0x79, 0x0a, 0x1f, 0x0a, 0x16, 0x74, 0x65, 0x6c, 0x65, 12 | 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x73, 0x64, 0x6b, 0x2e, 0x6c, 13 | 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x05, 0x0a, 0x03, 14 | 0x63, 0x70, 0x70, 0x0a, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 15 | 0x69, 0x63, 0x65, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x11, 0x0a, 16 | 0x0f, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x65, 17 | 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x11, 0x0a, 0x0f, 18 | 0x6f, 0x72, 0x67, 0x2e, 0x72, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 19 | 0x63, 0x74, 0x2e, 0x52, 0x12, 0x23, 0x09, 0x00, 0x7a, 0x6e, 0x4c, 20 | 0xb0, 0x5f, 0x4e, 0x18, 0x10, 0x09, 0x1a, 0x04, 0x49, 0x4e, 0x46, 21 | 0x4f, 0x2a, 0x07, 0x0a, 0x05, 0x54, 0x65, 0x73, 0x74, 0x21, 0x59, 22 | 0xd0, 0xb3, 0x72, 0x4c, 0xb0, 0x5f, 0x4e, 0x18 23 | )) 24 | 25 | logs <- ccall(otel_parse_log_record, logmsg) 26 | lrec <- logs[[1]]$scope_logs[[1]]$log_records[[1]] 27 | expect_true(is.double(lrec$time_stamp)) 28 | expect_true(length(lrec$time_stamp) == 1) 29 | expect_true(is.double(lrec$observed_time_stamp)) 30 | expect_true(length(lrec$observed_time_stamp) == 1) 31 | lrec <- lrec[setdiff(names(lrec), c("time_stamp", "observed_time_stamp"))] 32 | expect_snapshot(lrec) 33 | 34 | logmsg[1] <- as.raw(0xff) 35 | expect_snapshot(error = TRUE, ccall(otel_parse_log_record, logmsg)) 36 | }) 37 | 38 | test_that("otel_decode_metrics_record_", { 39 | # fmt: skip 40 | metmsg <- as.raw(c( 41 | 0x0a, 0xd0, 0x01, 0x0a, 0x8e, 0x01, 0x0a, 0x21, 0x0a, 0x15, 0x74, 42 | 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x73, 0x64, 43 | 0x6b, 0x2e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x08, 44 | 0x0a, 0x06, 0x31, 0x2e, 0x32, 0x31, 0x2e, 0x30, 0x0a, 0x25, 0x0a, 45 | 0x12, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 46 | 0x73, 0x64, 0x6b, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0f, 0x0a, 47 | 0x0d, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 48 | 0x74, 0x72, 0x79, 0x0a, 0x1f, 0x0a, 0x16, 0x74, 0x65, 0x6c, 0x65, 49 | 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x73, 0x64, 0x6b, 0x2e, 0x6c, 50 | 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x05, 0x0a, 0x03, 51 | 0x63, 0x70, 0x70, 0x0a, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 52 | 0x69, 0x63, 0x65, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x11, 0x0a, 53 | 0x0f, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x65, 54 | 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x11, 0x0a, 0x0f, 55 | 0x6f, 0x72, 0x67, 0x2e, 0x72, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 56 | 0x63, 0x74, 0x2e, 0x52, 0x12, 0x28, 0x0a, 0x03, 0x63, 0x74, 0x72, 57 | 0x3a, 0x21, 0x0a, 0x1b, 0x11, 0x18, 0xea, 0x6d, 0x23, 0x04, 0x61, 58 | 0x4e, 0x18, 0x19, 0x60, 0x97, 0xe1, 0x23, 0x04, 0x61, 0x4e, 0x18, 59 | 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x40, 0x10, 0x02, 60 | 0x18, 0x01 61 | )) 62 | 63 | mets <- ccall(otel_parse_metrics_record, metmsg) 64 | expect_snapshot(mets) 65 | 66 | metmsg[1] <- as.raw(0xff) 67 | expect_snapshot(error = TRUE, ccall(otel_parse_metrics_record, metmsg)) 68 | }) 69 | 70 | test_that("otel_encode_response_", { 71 | expect_snapshot(encode_response("traces")) 72 | expect_snapshot(encode_response( 73 | "traces", 74 | "partial-success", 75 | error_message = "partial fail!", 76 | rejected = 1L 77 | )) 78 | expect_snapshot(encode_response( 79 | "traces", 80 | "failure", 81 | error_message = "fail!", 82 | error_code = 2L 83 | )) 84 | 85 | expect_snapshot(encode_response("metrics")) 86 | expect_snapshot(encode_response( 87 | "metrics", 88 | "partial-success", 89 | error_message = "partial fail!", 90 | rejected = 1L 91 | )) 92 | 93 | expect_snapshot(encode_response("logs")) 94 | expect_snapshot(encode_response( 95 | "logs", 96 | "partial-success", 97 | error_message = "partial fail!", 98 | rejected = 1L 99 | )) 100 | }) 101 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/logger.md: -------------------------------------------------------------------------------- 1 | # get_default_log_severity 2 | 3 | Code 4 | get_default_log_severity() 5 | Condition 6 | Error in `get_default_log_severity()`: 7 | ! Invalid OpenTelemetry log level from the OTEL_LOG_LEVEL environment variable. Must be one of trace, trace2, trace3, trace4, debug, debug2, debug3, debug4, info, info2, info3, info4, warn, warn2, warn3, warn4, error, error2, error3, error4, fatal, fatal2, fatal3, fatal4, but it is '25'. 8 | 9 | --- 10 | 11 | Code 12 | get_default_log_severity() 13 | Condition 14 | Error in `get_default_log_severity()`: 15 | ! Invalid OpenTelemetry log level from the OTEL_LOG_LEVEL environment variable. Must be one of trace, trace2, trace3, trace4, debug, debug2, debug3, debug4, info, info2, info3, info4, warn, warn2, warn3, warn4, error, error2, error3, error4, fatal, fatal2, fatal3, fatal4, but it is 'whatup'. 16 | 17 | # log_severity_levels_spec 18 | 19 | Code 20 | log_severity_levels_spec() 21 | Output 22 | invalid trace trace2 trace3 trace4 23 | 0 1 2 3 4 24 | debug debug2 debug3 debug4 info 25 | 5 6 7 8 9 26 | info2 info3 info4 warn warn2 27 | 10 11 12 13 14 28 | warn3 warn4 error error2 error3 29 | 15 16 17 18 19 30 | error4 fatal fatal2 fatal3 fatal4 31 | 20 21 22 23 24 32 | maximumseverity 33 | 255 34 | 35 | # otel_logger_provider_flush 36 | 37 | Code 38 | ccall(otel_logger_provider_flush, 1L) 39 | Condition 40 | Warning: 41 | OpenTelemetry: invalid logger provider pointer. 42 | Output 43 | NULL 44 | Code 45 | ccall(otel_logger_provider_flush, x) 46 | Condition 47 | Error: 48 | ! Opentelemetry logger provider cleaned up already, internal error. 49 | 50 | # otel_get_logger 51 | 52 | Code 53 | ccall(otel_get_logger, 1L, "foo", 1L, NULL, NULL, NULL) 54 | Condition 55 | Error: 56 | ! OpenTelemetry: invalid logger provider pointer. 57 | Code 58 | ccall(otel_get_logger, x, "foo", 1L, NULL, NULL, NULL) 59 | Condition 60 | Error: 61 | ! Opentelemetry logger provider cleaned up already, internal error. 62 | 63 | # otel_get_minimum_log_severity 64 | 65 | Code 66 | ccall(otel_get_minimum_log_severity, 1L) 67 | Condition 68 | Error: 69 | ! Opentelemetry: invalid logger pointer 70 | Code 71 | ccall(otel_get_minimum_log_severity, x) 72 | Condition 73 | Error: 74 | ! Opentelemetry logger cleaned up already, internal error. 75 | 76 | # otel_set_minimum_log_severity 77 | 78 | Code 79 | ccall(otel_set_minimum_log_severity, 1L, 1L) 80 | Condition 81 | Error: 82 | ! Opentelemetry: invalid logger pointer 83 | Code 84 | ccall(otel_set_minimum_log_severity, x, 1L) 85 | Condition 86 | Error: 87 | ! Opentelemetry logger cleaned up already, internal error. 88 | 89 | # otel_logger_get_name 90 | 91 | Code 92 | ccall(otel_logger_get_name, 1L) 93 | Condition 94 | Error: 95 | ! Opentelemetry: invalid logger pointer 96 | Code 97 | ccall(otel_logger_get_name, x) 98 | Condition 99 | Error: 100 | ! Opentelemetry logger cleaned up already, internal error. 101 | 102 | # otel_logger_is_enabled 103 | 104 | Code 105 | ccall(otel_logger_is_enabled, 1L, 1L, NULL) 106 | Condition 107 | Error: 108 | ! Opentelemetry: invalid logger pointer 109 | Code 110 | ccall(otel_logger_is_enabled, x, 1L, NULL) 111 | Condition 112 | Error: 113 | ! Opentelemetry logger cleaned up already, internal error. 114 | 115 | # otel_log 116 | 117 | Code 118 | ccall(otel_log, 1L, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL) 119 | Condition 120 | Error: 121 | ! Opentelemetry: invalid logger pointer 122 | Code 123 | ccall(otel_log, x, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL) 124 | Condition 125 | Error: 126 | ! Opentelemetry logger cleaned up already, internal error. 127 | 128 | -------------------------------------------------------------------------------- /R/collapse.R: -------------------------------------------------------------------------------- 1 | collapse <- function( 2 | x, 3 | sep = ", ", 4 | sep2 = sub("^,", "", last), 5 | last = ", and ", 6 | trunc = Inf, 7 | width = Inf, 8 | ellipsis = "...", 9 | style = c("both-ends", "head") 10 | ) { 11 | style <- match.arg(style) 12 | switch( 13 | style, 14 | "both-ends" = collapse_both_ends( 15 | x, 16 | sep, 17 | sep2, 18 | last, 19 | trunc, 20 | width, 21 | ellipsis 22 | ), 23 | "head" = collapse_head(x, sep, sep2, last, trunc, width, ellipsis) 24 | ) 25 | } 26 | 27 | collapse_head_notrim <- function(x, trunc, sep, sep2, last, ellipsis) { 28 | lnx <- length(x) 29 | 30 | if (lnx == 1L) { 31 | return(x) 32 | } 33 | if (lnx == 2L) { 34 | return(paste0(x, collapse = sep2)) 35 | } 36 | if (lnx <= trunc) { 37 | # no truncation 38 | return(paste0( 39 | paste(x[1:(lnx - 1L)], collapse = sep), 40 | last, 41 | x[lnx] 42 | )) 43 | } else { 44 | # truncation, no need for 'last' 45 | return(paste0( 46 | paste(x[1:trunc], collapse = sep), 47 | sep, 48 | ellipsis 49 | )) 50 | } 51 | } 52 | 53 | collapse_head <- function(x, sep, sep2, last, trunc, width, ellipsis) { 54 | trunc <- max(trunc, 1L) 55 | x <- as.character(x) 56 | lnx <- length(x) 57 | 58 | # special cases that do not need trimming 59 | if (lnx == 0L) { 60 | return("") 61 | } else if (anyNA(x)) { 62 | return(NA_character_) 63 | } 64 | 65 | # easier case, no width trimming 66 | if (width == Inf) { 67 | return(collapse_head_notrim(x, trunc, sep, sep2, last, ellipsis)) 68 | } 69 | 70 | # complex case, with width wrapping 71 | # first we truncate 72 | tcd <- lnx > trunc 73 | if (tcd) { 74 | x <- x[1:trunc] 75 | } 76 | 77 | # then we calculate the width w/o trimming 78 | wx <- nchar(x) 79 | wsep <- nchar(sep, "width") 80 | wsep2 <- nchar(sep2, "width") 81 | wlast <- nchar(last, "width") 82 | well <- nchar(ellipsis, "width") 83 | if (!tcd) { 84 | # x[1] 85 | # x[1] and x[2] 86 | # x[1], x[2], and x[3] 87 | nsep <- if (lnx > 2L) lnx - 2L else 0L 88 | nsep2 <- if (lnx == 2L) 1L else 0L 89 | nlast <- if (lnx > 2L) 1L else 0L 90 | wtot <- sum(wx) + nsep * wsep + nsep2 * wsep2 + nlast * wlast 91 | if (wtot <= width) { 92 | if (lnx == 1L) { 93 | return(x) 94 | } else if (lnx == 2L) { 95 | return(paste0(x, collapse = sep2)) 96 | } else { 97 | return(paste0( 98 | paste(x[1:(lnx - 1L)], collapse = sep), 99 | last, 100 | x[lnx] 101 | )) 102 | } 103 | } 104 | } else { 105 | # x[1], x[2], x[trunc], ... 106 | wtot <- sum(wx) + trunc * wsep + well 107 | if (wtot <= width) { 108 | return(paste0( 109 | paste(x, collapse = sep), 110 | sep, 111 | ellipsis 112 | )) 113 | } 114 | } 115 | 116 | # we need to find the longest possible truncation for the form 117 | # x[1], x[2], x[trunc], ... 118 | # each item is wx + wsep wide, so we search how many fits, with ellipsis 119 | last <- function(x) if (length(x) >= 1L) x[length(x)] else x[NA_integer_] 120 | trunc <- last(which(cumsum(wx + wsep) + well <= width)) 121 | 122 | # not even one element fits 123 | if (is.na(trunc)) { 124 | if (well > width) { 125 | return(strtrim(ellipsis, width)) 126 | } else if (well == width) { 127 | return(ellipsis) 128 | } else if (well + wsep >= width) { 129 | return(paste0(strtrim(x[1L], width), ellipsis)) 130 | } else { 131 | return(paste0( 132 | strtrim(x[1L], max(width - well - wsep, 0L)), 133 | sep, 134 | ellipsis 135 | )) 136 | } 137 | } 138 | 139 | return(paste0( 140 | paste(x[1:trunc], collapse = sep), 141 | sep, 142 | ellipsis 143 | )) 144 | } 145 | 146 | collapse_both_ends <- function(x, sep, sep2, last, trunc, width, ellipsis) { 147 | # we always list at least 5 elements 148 | trunc <- max(trunc, 5L) 149 | trunc <- min(trunc, length(x)) 150 | if (length(x) <= 5L || length(x) <= trunc) { 151 | return(collapse_head(x, sep, sep2, last, trunc = trunc, width, ellipsis)) 152 | } 153 | 154 | # we have at least six elements in the vector 155 | # 1, 2, 3, ..., 9, and 10 156 | x <- as.character(c(x[1:(trunc - 2L)], x[length(x) - 1L], x[length(x)])) 157 | paste0( 158 | c(x[1:(trunc - 2L)], ellipsis, paste0(x[trunc - 1L], last, x[trunc])), 159 | collapse = sep 160 | ) 161 | } 162 | 163 | trim <- function(x) { 164 | has_newline <- function(x) any(grepl("\\n", x)) 165 | if (length(x) == 0L || !has_newline(x)) { 166 | return(x) 167 | } 168 | ccall(trim_, x) 169 | } 170 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # otelsdk 5 | 6 | > OpenTelemetry SDK for R packages and projects 7 | 8 | 9 | 10 | ![lifecycle](https://lifecycle.r-lib.org/articles/figures/lifecycle-experimental.svg) 11 | [![R-CMD-check](https://github.com/r-lib/otelsdk/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/otelsdk/actions/workflows/R-CMD-check.yaml) 12 | [![Codecov test 13 | coverage](https://codecov.io/gh/r-lib/otelsdk/graph/badge.svg?token=GAqo3S38e7)](https://app.codecov.io/gh/r-lib/otelsdk) 14 | 15 | 16 | OpenTelemetry is an observability framework. 17 | [OpenTelemetry](https://opentelemetry.io/) is a collection of tools, 18 | APIs, and SDKs used to instrument, generate, collect, and export 19 | telemetry data such as metrics, logs, and traces, for analysis in order 20 | to understand your software’s performance and behavior. 21 | 22 | For an introduction to OpenTelemetry, see the [OpenTelemetry website 23 | docs](https://opentelemetry.io/docs/). 24 | 25 | To learn how to instrument your R code, see [Getting 26 | Started](https://otel.r-lib.org/reference/gettingstarted.html) in the 27 | otel package. 28 | 29 | To learn how to collect telemetry data from an instrumented R package or 30 | project, see [Collecting Telemetry 31 | Data](https://otelsdk.r-lib.org/reference/collecting.html). 32 | 33 | For project status, installation instructions and more, read on. 34 | 35 | ## Features 36 | 37 | - Lightweight packages. otel is a small R package without dependencies 38 | and compiled code. otelsdk needs a C++11 compiler and otel. 39 | - Minimal performance impact when tracing is disabled. otel functions do 40 | not evaluate their arguments in this case. 41 | - Zero-code instrumentation support. Add tracing to (some) functions of 42 | selected packages automatically. 43 | - Configuration via environment variables. 44 | - Minimal extra code. Add tracing to a function with a single extra 45 | function call. 46 | - Production mode: otel functions do not crash your production app in 47 | production mode. 48 | - Development mode: otel functions error early in development mode. 49 | 50 | ## The otel and otelsdk R packages 51 | 52 | Use the [otel](https://github.com/r-lib/otel) package as a dependency if 53 | you want to instrument your R package or project for OpenTelemetry. 54 | 55 | Use the [otelsdk](https://github.com/r-lib/otelsdk) package to produce 56 | OpenTelemetry output from an R package or project that was instrumented 57 | with the otel package. 58 | 59 | ## Reference Documentation 60 | 61 | - otel: 62 | - otelsdk: 63 | 64 | ## Status 65 | 66 | The current status of the major functional components for OpenTelemetry 67 | R is as follows: 68 | 69 | | *Traces* | *Metrics* | *Logs* | 70 | |-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| 71 | | [Development](https://opentelemetry.io/docs/specs/otel/versioning-and-stability/#development) | [Development](https://opentelemetry.io/docs/specs/otel/versioning-and-stability/#development) | [Development](https://opentelemetry.io/docs/specs/otel/versioning-and-stability/#development) | 72 | 73 | ## Version support 74 | 75 | otel and otelsdk support R 3.6.0 and higher on Unix and R 4.3.0 or 76 | higher on Windows. 77 | 78 | ## Installation 79 | 80 | You can install the otel from CRAN: 81 | 82 | ``` r 83 | install.packages("otelsdk") 84 | ``` 85 | 86 | ### Compiling from source 87 | 88 | To compile otelsdk from source, you need to install the protobuf library 89 | first: 90 | 91 | - On Windows install the correct version of 92 | [Rtools](https://cran.r-project.org/bin/windows/Rtools/). 93 | 94 | - On Linux install the appropriate package from your distribution. 95 | 96 | - On macOS, you can use CRAN’s [protobuf 97 | build](https://mac.r-project.org/bin/) or Homebrew. If you are using 98 | CRAN’s build, then you must uninstall or unlink Homebrew protobuf: 99 | 100 | brew unlink protobuf 101 | 102 | ## Repositories 103 | 104 | - otel: 105 | - otelsdk: 106 | 107 | ## License 108 | 109 | MIT © Posit, PBC 110 | -------------------------------------------------------------------------------- /tests/testthat/test-print.R: -------------------------------------------------------------------------------- 1 | test_that("generic_print", { 2 | fake(generic_print, "format", function(x, ...) paste("line", x)) 3 | expect_snapshot(out <- generic_print(1:3)) 4 | expect_equal(out, 1:3) 5 | }) 6 | 7 | test_that("generic_format", { 8 | x <- structure(list(a = 1, b = "foo"), class = "cls") 9 | expect_snapshot(writeLines(generic_format(x))) 10 | 11 | x <- structure(list( 12 | a = 1, 13 | attr = structure(list(x = 1:10), class = "otel_attributes"), 14 | b = "foo" 15 | )) 16 | expect_snapshot(writeLines(generic_format(x))) 17 | }) 18 | 19 | test_that("with_width", { 20 | expect_snapshot(with_width(print(1:30), 40)) 21 | }) 22 | 23 | test_that("format.trace_flags", { 24 | expect_snapshot({ 25 | tf1 <- structure( 26 | c(sampled = TRUE, random = TRUE), 27 | class = "otel_trace_flags" 28 | ) 29 | format(tf1) 30 | tf2 <- structure( 31 | c(sampled = FALSE, random = FALSE), 32 | class = "otel_trace_flags" 33 | ) 34 | format(tf2) 35 | }) 36 | }) 37 | 38 | test_that("format.otel_attributes", { 39 | expect_snapshot( 40 | writeLines(format(structure( 41 | list(), 42 | names = character(), 43 | class = "otel_attributes" 44 | ))) 45 | ) 46 | expect_snapshot( 47 | writeLines(format(structure( 48 | list(a = "this", b = 1:4), 49 | class = "otel_attributes" 50 | ))) 51 | ) 52 | }) 53 | 54 | test_that("format.otel_span_data, print.otel_span_data", { 55 | spns <- with_otel_record(function() { 56 | otel::start_local_active_span("s", tracer = "org.r-lib.otel") 57 | trc <- otel::get_tracer("org.r-lib.otel") 58 | })[["traces"]] 59 | 60 | expect_snapshot( 61 | spns[["s"]], 62 | transform = transform_span_data 63 | ) 64 | }) 65 | 66 | test_that("format.otel_instrumentation_scope_data", { 67 | spns <- with_otel_record(function() { 68 | otel::start_local_active_span("s", tracer = "org.r-lib.otel") 69 | trc <- otel::get_tracer("org.r-lib.otel") 70 | })[["traces"]] 71 | 72 | expect_snapshot(writeLines( 73 | format(spns[["s"]][["instrumentation_scope"]]) 74 | )) 75 | 76 | tp <- tracer_provider_memory_new() 77 | trc <- tp$get_tracer( 78 | "org.r-lib.otel", 79 | version = "0.1.0", 80 | schema_url = "https://opentelemetry.io/schemas/1.13.0", 81 | attributes = list(foo = 1:5, bar = "that") 82 | ) 83 | sp <- trc$start_local_active_span("s") 84 | sp$end() 85 | spns <- tp$get_spans() 86 | 87 | expect_snapshot( 88 | spns[["s"]][["instrumentation_scope"]] 89 | ) 90 | }) 91 | 92 | test_that("format.otel_sum_point_data", { 93 | skip("unreliable") 94 | mp <- meter_provider_memory_new() 95 | mtr <- mp$get_meter() 96 | ctr <- mtr$create_counter("c") 97 | ctr$add(5) 98 | mp$flush() 99 | mp$shutdown() 100 | mtrs <- mp$get_metrics() 101 | # there are two reports, and the first one might be empty, 102 | # but this depends on the platforms and probably chance, so skip it 103 | expect_snapshot( 104 | mtrs[[2]], 105 | transform = transform_metric_data 106 | ) 107 | }) 108 | 109 | test_that("format.otel_histogram_point_data", { 110 | skip("unreliable") 111 | mp <- meter_provider_memory_new() 112 | mtr <- mp$get_meter() 113 | hst <- mtr$create_histogram("h") 114 | for (i in 1:10) { 115 | hst$record(i) 116 | } 117 | mp$flush() 118 | mp$shutdown() 119 | mtrs <- mp$get_metrics() 120 | # there are two reports, and the first one might be empty, 121 | # but this depends on the platforms and probably chance, so skip it 122 | expect_snapshot( 123 | mtrs[[2]], 124 | transform = transform_metric_data 125 | ) 126 | }) 127 | 128 | test_that("format.otel_last_value_point_data", { 129 | skip("unreliable") 130 | mp <- meter_provider_memory_new() 131 | mtr <- mp$get_meter() 132 | gge <- mtr$create_gauge("g") 133 | gge$record(5) 134 | mp$flush() 135 | mp$shutdown() 136 | mtrs <- mp$get_metrics() 137 | # there are two reports, and the first one might be empty, 138 | # but this depends on the platforms and probably chance, so skip it 139 | expect_snapshot( 140 | mtrs[[2]], 141 | transform = transform_metric_data 142 | ) 143 | }) 144 | 145 | test_that("format.otel_drop_point_data", { 146 | x <- structure(list(), class = "otel_drop_point_data") 147 | expect_snapshot(x) 148 | }) 149 | 150 | test_that("format.otel_metrics_data", { 151 | mp <- meter_provider_memory_new() 152 | mtr <- mp$get_meter() 153 | ctr <- mtr$create_counter("c") 154 | ctr$add(5) 155 | mp$flush() 156 | mp$shutdown() 157 | mtrs <- mp$get_metrics() 158 | # there are two reports, and the first one might be empty, 159 | # but this depends on the platforms and probably chance, so skip it 160 | mtrs[[1]] <- NULL 161 | expect_snapshot( 162 | mtrs, 163 | transform = transform_metric_data 164 | ) 165 | }) 166 | --------------------------------------------------------------------------------