From 483aaf19f277596ae073571061d4e0048c2463e0 Mon Sep 17 00:00:00 2001 From: Tiit Hansen Date: Sun, 7 May 2023 14:34:21 +0300 Subject: [PATCH] Improve tracing 1) Add possibility to use HTTP OTLP exporter. 2) Improve tracing so that module creates internal or client span depending if the request is handled by nginx directly or passed upstream. 3) Set span status depending on result. If status code is between 400 - 599 set status as error. 4) Catch internal processing failures in log phase. 5) Rewrite tests in JS --- .github/workflows/nginx.yml | 16 +- instrumentation/nginx/CMakeLists.txt | 3 + instrumentation/nginx/src/agent_config.cpp | 32 +- instrumentation/nginx/src/agent_config.h | 2 + instrumentation/nginx/src/otel_ngx_module.cpp | 161 +- .../nginx/src/post_batch_span_processor.cpp | 8 + .../nginx/src/post_batch_span_processor.h | 27 + .../nginx/src/post_span_processor.cpp | 8 + .../nginx/src/post_span_processor.h | 26 + instrumentation/nginx/src/proxy_recordable.h | 83 + instrumentation/nginx/src/trace_context.h | 4 + instrumentation/nginx/test/conf/nginx.conf | 3 +- instrumentation/nginx/test/data/trace.json | 2 - .../lib/mix/tasks/dockerfiles.ex | 2 +- .../test/instrumentation/package-lock.json | 1944 +++++++++++++++++ .../nginx/test/instrumentation/package.json | 14 + .../nginx/test/instrumentation/test.js | 782 +++++++ 17 files changed, 3063 insertions(+), 54 deletions(-) create mode 100644 instrumentation/nginx/src/post_batch_span_processor.cpp create mode 100644 instrumentation/nginx/src/post_batch_span_processor.h create mode 100644 instrumentation/nginx/src/post_span_processor.cpp create mode 100644 instrumentation/nginx/src/post_span_processor.h create mode 100644 instrumentation/nginx/src/proxy_recordable.h create mode 100644 instrumentation/nginx/test/instrumentation/package-lock.json create mode 100644 instrumentation/nginx/test/instrumentation/package.json create mode 100644 instrumentation/nginx/test/instrumentation/test.js diff --git a/.github/workflows/nginx.yml b/.github/workflows/nginx.yml index 130fdd3e6..87e984566 100644 --- a/.github/workflows/nginx.yml +++ b/.github/workflows/nginx.yml @@ -70,10 +70,22 @@ jobs: rm -rf /tmp/buildx-cache/nginx mv /tmp/buildx-cache/express-new /tmp/buildx-cache/express mv /tmp/buildx-cache/nginx-new /tmp/buildx-cache/nginx - - name: run tests + #- name: run tests + # run: | + # cd instrumentation/nginx/test/instrumentation + # mix test + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: '18' + - name: Install dependencies + run: | + cd instrumentation/nginx/test/instrumentation + npm install + - name: Run tests run: | cd instrumentation/nginx/test/instrumentation - mix test + npm run test - name: copy artifacts id: artifacts run: | diff --git a/instrumentation/nginx/CMakeLists.txt b/instrumentation/nginx/CMakeLists.txt index 82924354a..ef2c65a47 100644 --- a/instrumentation/nginx/CMakeLists.txt +++ b/instrumentation/nginx/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.12) project(opentelemetry-nginx) +find_package(nlohmann_json REQUIRED) find_package(opentelemetry-cpp REQUIRED) find_package(Threads REQUIRED) find_package(Protobuf REQUIRED) @@ -19,6 +20,8 @@ add_library(otel_ngx_module SHARED src/otel_ngx_module_modules.c src/propagate.cpp src/script.cpp + src/post_batch_span_processor.cpp + src/post_span_processor.cpp ) target_compile_options(otel_ngx_module diff --git a/instrumentation/nginx/src/agent_config.cpp b/instrumentation/nginx/src/agent_config.cpp index 2921c2a4d..0d23e9ed8 100644 --- a/instrumentation/nginx/src/agent_config.cpp +++ b/instrumentation/nginx/src/agent_config.cpp @@ -2,6 +2,7 @@ #include "toml.h" #include #include +#include struct ScopedTable { ScopedTable(toml_table_t* table) : table(table) {} @@ -34,13 +35,38 @@ static bool SetupOtlpExporter(toml_table_t* table, ngx_log_t* log, OtelNgxAgentC } std::string host = FromStringDatum(hostVal); + opentelemetry::ext::http::common::UrlParser urlParser(host); - if (!portVal.ok) { - ngx_log_error(NGX_LOG_ERR, log, 0, "Missing required port field for OTLP exporter"); + if (!urlParser.success_) { + ngx_log_error(NGX_LOG_ERR, log, 0, "Invalid host field for OTLP exporter"); return false; } - config->exporter.endpoint = host + ":" + std::to_string(portVal.u.i);; + if (portVal.ok) { + urlParser.port_ = portVal.u.i; + } + + config->exporter.endpoint = + urlParser.scheme_ + "://" + + urlParser.host_ + ":" + + std::to_string(urlParser.port_) + + urlParser.path_ + + urlParser.query_; + + ngx_log_error(NGX_LOG_INFO, log, 0, "Using host: %s", config->exporter.endpoint.c_str()); + + const toml_datum_t protocolVal = toml_string_in(table, "protocol"); + + if (protocolVal.ok) { + std::string protocolStringVal = FromStringDatum(protocolVal); + if (protocolStringVal == "grpc") { + config->exporter.protocol = gRPC; + } else if (protocolStringVal == "http") { + config->exporter.protocol = HTTP; + } else { + config->exporter.protocol = gRPC; + } + } toml_datum_t useSSLVal = toml_bool_in(table, "use_ssl"); if (useSSLVal.ok) { diff --git a/instrumentation/nginx/src/agent_config.h b/instrumentation/nginx/src/agent_config.h index 1f447ddb4..2ad0548b5 100644 --- a/instrumentation/nginx/src/agent_config.h +++ b/instrumentation/nginx/src/agent_config.h @@ -7,12 +7,14 @@ extern "C" { } enum OtelExporterType { OtelExporterOTLP, OtelExporterJaeger }; +enum OtelExporterProtocol { gRPC, HTTP }; enum OtelProcessorType { OtelProcessorSimple, OtelProcessorBatch }; enum OtelSamplerType { OtelSamplerAlwaysOn, OtelSamplerAlwaysOff, OtelSamplerTraceIdRatioBased }; struct OtelNgxAgentConfig { struct { OtelExporterType type = OtelExporterOTLP; + OtelExporterProtocol protocol = gRPC; std::string endpoint; bool use_ssl_credentials = false; std::string ssl_credentials_cacert_path = ""; diff --git a/instrumentation/nginx/src/otel_ngx_module.cpp b/instrumentation/nginx/src/otel_ngx_module.cpp index b94b9725e..b2e40b60a 100644 --- a/instrumentation/nginx/src/otel_ngx_module.cpp +++ b/instrumentation/nginx/src/otel_ngx_module.cpp @@ -3,6 +3,7 @@ // avoid conflict between Abseil library and OpenTelemetry C++ absl::variant. // https://github.com/open-telemetry/opentelemetry-cpp/tree/main/examples/otlp#additional-notes-regarding-abseil-library #include +#include // clang-format on #include @@ -24,6 +25,8 @@ extern ngx_module_t otel_ngx_module; #include "nginx_config.h" #include "nginx_utils.h" #include "propagate.h" +#include "post_span_processor.h" +#include "post_batch_span_processor.h" #include #include #include @@ -37,6 +40,7 @@ extern ngx_module_t otel_ngx_module; #include namespace trace = opentelemetry::trace; +namespace common = opentelemetry::common; namespace nostd = opentelemetry::nostd; namespace sdktrace = opentelemetry::sdk::trace; namespace otlp = opentelemetry::exporter::otlp; @@ -133,6 +137,8 @@ static ngx_int_t OtelGetContextVar(ngx_http_request_t*, ngx_http_variable_value_ return NGX_OK; } + + static ngx_int_t OtelGetTraceContextVar(ngx_http_request_t* req, ngx_http_variable_value_t* v, uintptr_t data); @@ -437,21 +443,13 @@ TraceContext* CreateTraceContext(ngx_http_request_t* req, ngx_http_variable_valu return context; } -ngx_int_t StartNgxSpan(ngx_http_request_t* req) { - if (!IsOtelEnabled(req)) { - return NGX_DECLINED; - } - - // Internal requests must be called from another location in nginx, that should already have a trace. Without this check, a call would generate an extra (unrelated) span without much information - if (req->internal) { - return NGX_DECLINED; - } +TraceContext* StartNgxTrace(ngx_http_request_t* req) { ngx_http_variable_value_t* val = ngx_http_get_indexed_variable(req, otel_ngx_variables[0].index); if (!val) { ngx_log_error(NGX_LOG_ERR, req->connection->log, 0, "Unable to find OpenTelemetry context"); - return NGX_DECLINED; + return nullptr; } TraceContext* context = CreateTraceContext(req, val); @@ -465,30 +463,43 @@ ngx_int_t StartNgxSpan(ngx_http_request_t* req) { incomingContext = ExtractContext(&carrier); } - trace::StartSpanOptions startOpts; - startOpts.kind = trace::SpanKind::kServer; - startOpts.parent = GetCurrentSpan(incomingContext); + const auto operationName = GetOperationName(req); + + std::initializer_list> commonAttributes = { + {"http.method", FromNgxString(req->method_name)}, + {"http.flavor", NgxHttpFlavor(req)}, + {"http.target", FromNgxString(req->unparsed_uri)}, + // Add http.route as its according to OpenTelemetry spec + {"http.route", FromNgxString(req->unparsed_uri)} + }; + + trace::StartSpanOptions requestStartOpts; + requestStartOpts.parent = GetCurrentSpan(incomingContext); + requestStartOpts.kind = trace::SpanKind::kServer; + context->request_span = GetTracer()->StartSpan(operationName,commonAttributes, requestStartOpts); - context->request_span = GetTracer()->StartSpan( - GetOperationName(req), - { - {"http.method", FromNgxString(req->method_name)}, - {"http.flavor", NgxHttpFlavor(req)}, - {"http.target", FromNgxString(req->unparsed_uri)}, - }, - startOpts); + trace::StartSpanOptions innerSpanOpts; + innerSpanOpts.parent = context->request_span->GetContext(); + innerSpanOpts.kind = trace::SpanKind::kInternal; + + context->inner_span = GetTracer()->StartSpan(operationName,commonAttributes,innerSpanOpts); nostd::string_view serverName = GetNgxServerName(req); if (!serverName.empty()) { context->request_span->SetAttribute("http.server_name", serverName); + context->inner_span->SetAttribute("http.server_name", serverName); } if (req->headers_in.host) { - context->request_span->SetAttribute("http.host", FromNgxString(req->headers_in.host->value)); + const auto host = FromNgxString(req->headers_in.host->value); + context->request_span->SetAttribute("http.host", host); + context->inner_span->SetAttribute("http.host", host); } if (req->headers_in.user_agent) { - context->request_span->SetAttribute("http.user_agent", FromNgxString(req->headers_in.user_agent->value)); + const auto userAgent = FromNgxString(req->headers_in.user_agent->value); + context->request_span->SetAttribute("http.user_agent", userAgent); + context->inner_span->SetAttribute("http.user_agent", userAgent); } if (locConf->captureHeaders) { @@ -500,10 +511,24 @@ ngx_int_t StartNgxSpan(ngx_http_request_t* req) { {excludedHeaders, 2}); } - auto outgoingContext = incomingContext.SetValue(trace::kSpanKey, context->request_span); + auto outgoingContext = incomingContext.SetValue(trace::kSpanKey, context->inner_span); InjectContext(&carrier, outgoingContext); + return context; +} + +ngx_int_t StartNgxSpan(ngx_http_request_t* req) { + if (!IsOtelEnabled(req)) { + return NGX_DECLINED; + } + + if (req->internal) { + return NGX_DECLINED; + } + + StartNgxTrace(req); + return NGX_DECLINED; } @@ -532,23 +557,13 @@ void AddScriptAttributes( } } -ngx_int_t FinishNgxSpan(ngx_http_request_t* req) { - if (!IsOtelEnabled(req)) { - return NGX_DECLINED; - } - - TraceContext* context = GetTraceContext(req); - - if (!context) { - return NGX_DECLINED; - } +void UpdateSpan(nostd::shared_ptr span, ngx_http_request_t* req, bool captureHeaders = false) { - auto span = context->request_span; span->SetAttribute("http.status_code", req->headers_out.status); - OtelNgxLocationConf* locConf = GetOtelLocationConf(req); + OtelNgxLocationConf *locConf = GetOtelLocationConf(req); - if (locConf->captureHeaders) { + if (locConf->captureHeaders && captureHeaders) { OtelCaptureHeaders(span, ngx_string("http.response.header."), &req->headers_out.headers, #if (NGX_PCRE) locConf->sensitiveHeaderNames, locConf->sensitiveHeaderValues, @@ -559,9 +574,49 @@ ngx_int_t FinishNgxSpan(ngx_http_request_t* req) { AddScriptAttributes(span.get(), GetOtelMainConf(req)->scriptAttributes, req); AddScriptAttributes(span.get(), locConf->customAttributes, req); + if (req->headers_out.status >= 400 && req->headers_out.status <= 599) { + span->SetStatus(trace::StatusCode::kError); + } + span->UpdateName(GetOperationName(req)); +} + +ngx_int_t FinishNgxSpan(ngx_http_request_t* req) { + if (!IsOtelEnabled(req)) { + return NGX_DECLINED; + } + + if (req->internal) { + return NGX_DECLINED; + } + + TraceContext* context = GetTraceContext(req); + + /* + * When nginx fails to process request (bad request, prematurely closed) then span is not started, here we can start + * a new span because there were no upstream calls anyway. + */ + if (!context) { + context = StartNgxTrace(req); + } + + auto span = context->request_span; + UpdateSpan(span, req, true); + + if (context->inner_span) { + + const bool hasUpstream = req->upstream != nullptr; + if (hasUpstream) { + context->inner_span->SetAttribute("span.kind", static_cast(trace::SpanKind::kClient)); + } + + UpdateSpan(context->inner_span, req); + + context->inner_span->End(); + } span->End(); + return NGX_DECLINED; } @@ -576,7 +631,7 @@ static ngx_int_t InitModule(ngx_conf_t* conf) { const PhaseHandler handlers[] = { {NGX_HTTP_REWRITE_PHASE, StartNgxSpan}, - {NGX_HTTP_LOG_PHASE, FinishNgxSpan}, + {NGX_HTTP_LOG_PHASE, FinishNgxSpan} }; for (const PhaseHandler& ph : handlers) { @@ -1019,11 +1074,26 @@ static std::unique_ptr CreateExporter(const OtelNgxAgent switch (conf->exporter.type) { case OtelExporterOTLP: { - std::string endpoint = conf->exporter.endpoint; - otlp::OtlpGrpcExporterOptions opts{endpoint}; - opts.use_ssl_credentials = conf->exporter.use_ssl_credentials; - opts.ssl_credentials_cacert_path = conf->exporter.ssl_credentials_cacert_path; - exporter.reset(new otlp::OtlpGrpcExporter(opts)); + + switch (conf->exporter.protocol) { + case gRPC: { + otlp::OtlpGrpcExporterOptions opts; + opts.endpoint = conf->exporter.endpoint; + + opts.use_ssl_credentials = conf->exporter.use_ssl_credentials; + opts.ssl_credentials_cacert_path = conf->exporter.ssl_credentials_cacert_path; + + exporter.reset(new otlp::OtlpGrpcExporter(opts)); + } + break; + case HTTP: { + otlp::OtlpHttpExporterOptions opts; + opts.url = conf->exporter.endpoint; + + exporter.reset(new otlp::OtlpHttpExporter(opts)); + } + break; + } break; } default: @@ -1035,6 +1105,7 @@ static std::unique_ptr CreateExporter(const OtelNgxAgent static std::unique_ptr CreateProcessor(const OtelNgxAgentConfig* conf, std::unique_ptr exporter) { + if (conf->processor.type == OtelProcessorBatch) { sdktrace::BatchSpanProcessorOptions opts; opts.max_queue_size = conf->processor.batch.maxQueueSize; @@ -1043,11 +1114,11 @@ CreateProcessor(const OtelNgxAgentConfig* conf, std::unique_ptrprocessor.batch.maxExportBatchSize; return std::unique_ptr( - new sdktrace::BatchSpanProcessor(std::move(exporter), opts)); + new PostBatchSpanProcessor(std::move(exporter), opts)); } return std::unique_ptr( - new sdktrace::SimpleSpanProcessor(std::move(exporter))); + new PostSpanProcessor(std::move(exporter))); } static std::unique_ptr CreateSampler(const OtelNgxAgentConfig* conf) { diff --git a/instrumentation/nginx/src/post_batch_span_processor.cpp b/instrumentation/nginx/src/post_batch_span_processor.cpp new file mode 100644 index 000000000..ad0398c95 --- /dev/null +++ b/instrumentation/nginx/src/post_batch_span_processor.cpp @@ -0,0 +1,8 @@ +#include "post_batch_span_processor.h" +#include + +void PostBatchSpanProcessor::OnEnd(std::unique_ptr &&span) noexcept +{ + ProxyRecordable* proxy = static_cast(span.get()); + BatchSpanProcessor::OnEnd(std::move(proxy->GetRealRecordable())); +} diff --git a/instrumentation/nginx/src/post_batch_span_processor.h b/instrumentation/nginx/src/post_batch_span_processor.h new file mode 100644 index 000000000..65c7ed6cc --- /dev/null +++ b/instrumentation/nginx/src/post_batch_span_processor.h @@ -0,0 +1,27 @@ +#ifndef OPENTELEMETRY_NGINX_POST_BATCH_SPAN_PROCESSOR_H +#define OPENTELEMETRY_NGINX_POST_BATCH_SPAN_PROCESSOR_H + +#include "proxy_recordable.h" +#include +#include + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace sdktrace = opentelemetry::sdk::trace; + +class PostBatchSpanProcessor : public sdktrace::BatchSpanProcessor { +public: + + explicit PostBatchSpanProcessor(std::unique_ptr &&exporter, + const sdktrace::BatchSpanProcessorOptions &options) noexcept + : BatchSpanProcessor(std::move(exporter), options) + {} + + void OnEnd(std::unique_ptr &&span) noexcept override; + + std::unique_ptr MakeRecordable() noexcept override { + return std::unique_ptr(new ProxyRecordable()); + } +}; + +#endif //OPENTELEMETRY_NGINX_POST_BATCH_SPAN_PROCESSOR_H diff --git a/instrumentation/nginx/src/post_span_processor.cpp b/instrumentation/nginx/src/post_span_processor.cpp new file mode 100644 index 000000000..58b917cae --- /dev/null +++ b/instrumentation/nginx/src/post_span_processor.cpp @@ -0,0 +1,8 @@ +#include "post_span_processor.h" + +void PostSpanProcessor::OnEnd(std::unique_ptr &&span) noexcept +{ + ProxyRecordable* proxy = static_cast(span.get()); + SimpleSpanProcessor::OnEnd(std::move(proxy->GetRealRecordable())); +} + diff --git a/instrumentation/nginx/src/post_span_processor.h b/instrumentation/nginx/src/post_span_processor.h new file mode 100644 index 000000000..19fd6326e --- /dev/null +++ b/instrumentation/nginx/src/post_span_processor.h @@ -0,0 +1,26 @@ +#ifndef OPENTELEMETRY_NGINX_POST_SPAN_PROCESSOR_H +#define OPENTELEMETRY_NGINX_POST_SPAN_PROCESSOR_H + +#include +#include +#include "proxy_recordable.h" + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace sdktrace = opentelemetry::sdk::trace; + +class PostSpanProcessor : public sdktrace::SimpleSpanProcessor { +public: + + explicit PostSpanProcessor(std::unique_ptr &&exporter) noexcept + : SimpleSpanProcessor(std::move(exporter)) + {} + + void OnEnd(std::unique_ptr &&span) noexcept override; + + std::unique_ptr MakeRecordable() noexcept override { + return std::unique_ptr(new ProxyRecordable()); + } +}; + +#endif //OPENTELEMETRY_NGINX_POST_SPAN_PROCESSOR_H diff --git a/instrumentation/nginx/src/proxy_recordable.h b/instrumentation/nginx/src/proxy_recordable.h new file mode 100644 index 000000000..3e36827ec --- /dev/null +++ b/instrumentation/nginx/src/proxy_recordable.h @@ -0,0 +1,83 @@ +#ifndef OPENTELEMETRY_NGINX_PROXY_RECORDABLE_H +#define OPENTELEMETRY_NGINX_PROXY_RECORDABLE_H + +#include + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace sdktrace = opentelemetry::sdk::trace; + +/** + * Wraps actual recordable object so we could override span.kind through SetAttribute. This workarounds the problem that + * in OpenTelemetry cpp SDK span kind is immutable on a span, but it causes problems for this instrumentation library. + */ +class ProxyRecordable : public sdktrace::Recordable { +public: + + ProxyRecordable() + : _realRecordable(new opentelemetry::exporter::otlp::OtlpRecordable()) { } + + std::unique_ptr GetRealRecordable() { + return std::unique_ptr(std::move(_realRecordable)); + } + + void SetIdentity(const opentelemetry::trace::SpanContext &span_context, + opentelemetry::trace::SpanId parent_span_id) noexcept override { + _realRecordable->SetIdentity(span_context, parent_span_id); + } + + virtual void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept override { + if (key == "span.kind") { + trace::SpanKind kind = static_cast(nostd::get(value)); + _realRecordable->SetSpanKind(kind); + } else { + _realRecordable->SetAttribute(key, value); + } + } + + void AddEvent(nostd::string_view name, + opentelemetry::common::SystemTimestamp timestamp, + const opentelemetry::common::KeyValueIterable &attributes) noexcept override { + _realRecordable->AddEvent(name, timestamp, attributes); + }; + + void AddLink(const opentelemetry::trace::SpanContext &span_context, + const opentelemetry::common::KeyValueIterable &attributes) noexcept override { + _realRecordable->AddLink(span_context, attributes); + }; + + void SetStatus(opentelemetry::trace::StatusCode code, + nostd::string_view description) noexcept override { + _realRecordable->SetStatus(code, description); + }; + + void SetName(nostd::string_view name) noexcept override { + _realRecordable->SetName(name); + }; + + void SetSpanKind(opentelemetry::trace::SpanKind span_kind) noexcept override { + _realRecordable->SetSpanKind(span_kind); + }; + + void SetResource(const opentelemetry::sdk::resource::Resource &resource) noexcept override { + _realRecordable->SetResource(resource); + }; + + void SetStartTime(opentelemetry::common::SystemTimestamp start_time) noexcept override { + _realRecordable->SetStartTime(start_time); + }; + + void SetDuration(std::chrono::nanoseconds duration) noexcept override { + _realRecordable->SetDuration(duration); + }; + + void SetInstrumentationScope(const sdktrace::InstrumentationScope &instrumentation_scope) noexcept override { + _realRecordable->SetInstrumentationScope(instrumentation_scope); + }; + +private: + std::unique_ptr _realRecordable; +}; + +#endif //OPENTELEMETRY_NGINX_PROXY_RECORDABLE_H diff --git a/instrumentation/nginx/src/trace_context.h b/instrumentation/nginx/src/trace_context.h index de128e024..7548f826d 100644 --- a/instrumentation/nginx/src/trace_context.h +++ b/instrumentation/nginx/src/trace_context.h @@ -23,7 +23,11 @@ struct TraceContext { TraceContext(ngx_http_request_t* req) : request(req), traceHeader{} {} /* The current request being handled by nginx. */ ngx_http_request_t* request; + /* Span used to record incoming requests */ opentelemetry::nostd::shared_ptr request_span; + /* Span used to record internal processing. This span could be internal or client + * depending if nginx handles request locally or calls upstream */ + opentelemetry::nostd::shared_ptr inner_span; /* Headers to be injected for the upstream request. */ TraceHeader traceHeader[2]; }; diff --git a/instrumentation/nginx/test/conf/nginx.conf b/instrumentation/nginx/test/conf/nginx.conf index 8da508660..aa4a73660 100644 --- a/instrumentation/nginx/test/conf/nginx.conf +++ b/instrumentation/nginx/test/conf/nginx.conf @@ -61,8 +61,9 @@ http { location = /capture_headers_with_sensitive_header_name { opentelemetry_capture_headers on; - opentelemetry_sensitive_header_names "header"; + opentelemetry_sensitive_header_names "header|token"; add_header Response-Header Foo; + add_header Response-Token Bar; return 200 ""; } diff --git a/instrumentation/nginx/test/data/trace.json b/instrumentation/nginx/test/data/trace.json index ef94ca9de..e69de29bb 100644 --- a/instrumentation/nginx/test/data/trace.json +++ b/instrumentation/nginx/test/data/trace.json @@ -1,2 +0,0 @@ -{"resourceSpans":[{"resource":{"attributes":[{"key":"telemetry.sdk.version","value":{"stringValue":"1.8.1"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.language","value":{"stringValue":"cpp"}},{"key":"service.name","value":{"stringValue":"nginx-proxy"}}]},"instrumentationLibrarySpans":[{"instrumentationLibrary":{"name":"nginx"},"spans":[{"traceId":"777151ac132afc1975887f8a3af71d23","spanId":"60bec6e1671cee32","parentSpanId":"","name":"otel_test","kind":"SPAN_KIND_SERVER","startTimeUnixNano":"1658331753935660143","endTimeUnixNano":"1658331753935827215","attributes":[{"key":"http.method","value":{"stringValue":"GET"}},{"key":"http.flavor","value":{"stringValue":"1.1"}},{"key":"http.target","value":{"stringValue":"/route_to_internal"}},{"key":"http.server_name","value":{"stringValue":"otel_test"}},{"key":"http.host","value":{"stringValue":"localhost:8000"}},{"key":"http.user_agent","value":{"stringValue":"curl/7.68.0"}},{"key":"http.status_code","value":{"intValue":"200"}},{"key":"http.scheme","value":{"stringValue":"http"}},{"key":"net.host.port","value":{"intValue":"8000"}},{"key":"net.peer.ip","value":{"stringValue":"172.25.0.1"}},{"key":"net.peer.port","value":{"intValue":"51608"}},{"key":"test.attrib.global","value":{"stringValue":"global"}},{"key":"test.attrib.custom","value":{"stringValue":"global-custom"}}],"status":{}}]}]}]} -{"resourceSpans":[{"resource":{"attributes":[{"key":"telemetry.sdk.version","value":{"stringValue":"1.8.1"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.language","value":{"stringValue":"cpp"}},{"key":"service.name","value":{"stringValue":"nginx-proxy"}}]},"instrumentationLibrarySpans":[{"instrumentationLibrary":{"name":"nginx"},"spans":[{"traceId":"579d297c39ddac461fbc50febd77f16a","spanId":"fecfc6a8c9b60f03","parentSpanId":"","name":"otel_test","kind":"SPAN_KIND_SERVER","startTimeUnixNano":"1658331755134563590","endTimeUnixNano":"1658331755134700342","attributes":[{"key":"http.method","value":{"stringValue":"GET"}},{"key":"http.flavor","value":{"stringValue":"1.1"}},{"key":"http.target","value":{"stringValue":"/route_to_internal"}},{"key":"http.server_name","value":{"stringValue":"otel_test"}},{"key":"http.host","value":{"stringValue":"localhost:8000"}},{"key":"http.user_agent","value":{"stringValue":"curl/7.68.0"}},{"key":"http.status_code","value":{"intValue":"200"}},{"key":"http.scheme","value":{"stringValue":"http"}},{"key":"net.host.port","value":{"intValue":"8000"}},{"key":"net.peer.ip","value":{"stringValue":"172.25.0.1"}},{"key":"net.peer.port","value":{"intValue":"51614"}},{"key":"test.attrib.global","value":{"stringValue":"global"}},{"key":"test.attrib.custom","value":{"stringValue":"global-custom"}}],"status":{}}]}]}]} \ No newline at end of file diff --git a/instrumentation/nginx/test/instrumentation/lib/mix/tasks/dockerfiles.ex b/instrumentation/nginx/test/instrumentation/lib/mix/tasks/dockerfiles.ex index c3300137c..00d2e948e 100644 --- a/instrumentation/nginx/test/instrumentation/lib/mix/tasks/dockerfiles.ex +++ b/instrumentation/nginx/test/instrumentation/lib/mix/tasks/dockerfiles.ex @@ -193,7 +193,7 @@ defmodule Mix.Tasks.Dockerfiles do -DCMAKE_PREFIX_PATH=/install \\ -DWITH_OTLP=ON \\ -DWITH_OTLP_GRPC=ON \\ - -DWITH_OTLP_HTTP=OFF \\ + -DWITH_OTLP_HTTP=ON \\ -DBUILD_TESTING=OFF \\ -DWITH_EXAMPLES=OFF \\ -DCMAKE_CXX_STANDARD=17 \\ diff --git a/instrumentation/nginx/test/instrumentation/package-lock.json b/instrumentation/nginx/test/instrumentation/package-lock.json new file mode 100644 index 000000000..252dce6da --- /dev/null +++ b/instrumentation/nginx/test/instrumentation/package-lock.json @@ -0,0 +1,1944 @@ +{ + "name": "instrumentation", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "instrumentation", + "version": "1.0.0", + "devDependencies": { + "axios": "^1.4.0", + "choma": "^1.2.1", + "docker-compose": "^0.24.0", + "mocha": "^10.2.0", + "tail": "^2.2.6" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/choma": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/choma/-/choma-1.2.1.tgz", + "integrity": "sha512-4KwEouEHt6SfG8vYnN2gSJfq/cGmnY2gubnUgsgkRXzHoSRAgluX2YXQgDg6bTDWuOmUrTb/cfwMpNlvnnPZCg==", + "dev": true, + "dependencies": { + "chalk": "^2.3.2", + "seedrandom": "^2.4.3" + }, + "peerDependencies": { + "mocha": ">=2" + } + }, + "node_modules/choma/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/choma/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/choma/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/choma/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/choma/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/choma/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/choma/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/docker-compose": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.0.tgz", + "integrity": "sha512-RN/oSPLPa6ZG5e4dHg8tD8EMpd1WJqomNMBQT+d2M5MwcmfrPW/xHTent4TVqX0zZvHemv7qhhNlzXjxCnFaQw==", + "dev": true, + "dependencies": { + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/seedrandom": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", + "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tail": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/tail/-/tail-2.2.6.tgz", + "integrity": "sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "choma": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/choma/-/choma-1.2.1.tgz", + "integrity": "sha512-4KwEouEHt6SfG8vYnN2gSJfq/cGmnY2gubnUgsgkRXzHoSRAgluX2YXQgDg6bTDWuOmUrTb/cfwMpNlvnnPZCg==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "seedrandom": "^2.4.3" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "docker-compose": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.0.tgz", + "integrity": "sha512-RN/oSPLPa6ZG5e4dHg8tD8EMpd1WJqomNMBQT+d2M5MwcmfrPW/xHTent4TVqX0zZvHemv7qhhNlzXjxCnFaQw==", + "dev": true, + "requires": { + "yaml": "^1.10.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "seedrandom": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", + "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tail": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/tail/-/tail-2.2.6.tgz", + "integrity": "sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/instrumentation/nginx/test/instrumentation/package.json b/instrumentation/nginx/test/instrumentation/package.json new file mode 100644 index 000000000..8e088e484 --- /dev/null +++ b/instrumentation/nginx/test/instrumentation/package.json @@ -0,0 +1,14 @@ +{ + "name": "instrumentation", + "version": "1.0.0", + "scripts": { + "test": "mocha --require choma test.js" + }, + "devDependencies": { + "axios": "^1.4.0", + "choma": "^1.2.1", + "docker-compose": "^0.24.0", + "mocha": "^10.2.0", + "tail": "^2.2.6" + } +} diff --git a/instrumentation/nginx/test/instrumentation/test.js b/instrumentation/nginx/test/instrumentation/test.js new file mode 100644 index 000000000..c06066c10 --- /dev/null +++ b/instrumentation/nginx/test/instrumentation/test.js @@ -0,0 +1,782 @@ +const { logs, upAll, stop } = require('docker-compose'); +const assert = require('assert'); +const path = require('path'); +const Tail = require('tail').Tail; +const axios = require('axios'); +const fs = require('fs').promises; + +const host = 'http://localhost:8000'; +const traceFile = path.join(__dirname, '../data/trace.json'); + +async function waitForStatup(name, startupCheck) { + return new Promise((resolve, reject) => { + logs(name, { follow: true, cwd: path.join(__dirname, '../'), callback: (chunk) => { + const line = chunk.toString('utf8'); + if (startupCheck(line)) { + resolve(); + } + }}) + .catch(reject); + }); +} + +async function startServicesAndWait(services) { + + // Make sure collector is able to write to trace.json + await fs.chmod(traceFile, 0666); + + return new Promise((resolve, reject) => { + upAll({ + cwd: path.join(__dirname, '../'), + // log: true, + }) + .then(async () => { + + await Promise.all( + services + .filter(service => service.startupCheck !== undefined) + .map(service => waitForStatup(service.name, service.startupCheck)) + ); + + resolve(); + }) + .catch(reject); + }); +} + +async function waitNginx() { + return new Promise(async (resolve) => { + while (true) { + try { + console.log(`Waiting for nginx to be up...`); + await axios.get(`${host}/up`); + resolve(); + break; + } catch (ex) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } + }); +} + +async function getTrace(traceStream, timeout ) { + return new Promise((resolve, reject) => { + + const listener = (line) => { + try { + const traceLine = JSON.parse(line); + resolve(traceLine); + } catch (ex) { + reject(ex); + } + } + + traceStream.once('line', listener); + + if (timeout) { + setTimeout(() => { + traceStream.removeListener('line', listener); + reject(new Error('timeout')); + }, timeout); + } + }); +} + +function attribValue(attribute) { + if (!attribute || !attribute.value) return undefined; + if (attribute.value.stringValue) return attribute.value.stringValue; + if (attribute.value.intValue) return parseInt(attribute.value.intValue); + if (attribute.value.arrayValue) return attribute.value.arrayValue.values.map((value) => attribValue({ value })); + else return undefined; +} + +function validateResourceAttributes(attributes, expectedAttributes) { + expectedAttributes.forEach((expectedAttribute) => { + + const value = attribValue(attributes.find((attrib) => attrib.key === expectedAttribute.key)); + + if (expectedAttribute.fn) { + assert(expectedAttribute.fn(value)); + } else if (expectedAttribute.regex) { + assert.match(value, expectedAttribute.regex); + } else { + if (Array.isArray(expectedAttribute.value)) { + assert.deepEqual(value, expectedAttribute.value); + } else { + assert.equal(value, expectedAttribute.value); + } + } + }); +} + +const commonExpectedAttributes = [ + // { key: 'service.name', value: 'nginx' }, + { key: 'telemetry.sdk.name', value: 'opentelemetry' }, + { key: 'telemetry.sdk.language', value: 'cpp' }, + { key: 'telemetry.sdk.version', value: '1.8.1' }, +]; + +describe('instrumentation-test', function () { + + traceStream = null; + + before('setup', async function () { + this.timeout(30000); + const services = [{ + name: 'collector', + startupCheck: (line) => line.includes('Everything is ready.') + }, { + name: 'node-backend', + startupCheck: (line) => line.includes('simple_express ready') + }, { + name: 'nginx', + }, { + name: 'php-backend', + }]; + + await startServicesAndWait(services); + + // Wait Nginx + await waitNginx(); + + // Open stream to ../data/trace.json + traceStream = new Tail(traceFile); + return; + }); + + after('cleanup', async function () { + this.timeout(30000); + traceStream.unwatch(); + await stop({ cwd: path.join(__dirname, '../') }).then(() => console.log('Stopped')); + }); + + it('HTTP upstream | resource attributes', async function () { + + this.timeout(5000); + + const res = await axios.get(`${host}/?foo=bar&x=42`); + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + assert.equal(trace.resourceSpans[0].instrumentationLibrarySpans.length, 1); + assert.equal(trace.resourceSpans[0].instrumentationLibrarySpans[0].spans.length, 1); + assert.equal(trace.resourceSpans[0].instrumentationLibrarySpans[0].spans[0].kind, "SPAN_KIND_CLIENT"); + + assert.equal(trace.resourceSpans[1].instrumentationLibrarySpans.length, 1); + assert.equal(trace.resourceSpans[1].instrumentationLibrarySpans[0].spans.length, 1); + assert.equal(trace.resourceSpans[1].instrumentationLibrarySpans[0].spans[0].kind, "SPAN_KIND_SERVER"); + + trace.resourceSpans.forEach((resourceSpan) => { + validateResourceAttributes(resourceSpan.resource.attributes, [ + ...commonExpectedAttributes, + { key: 'service.name', value: 'nginx-proxy' } + ]); + }); + }) + + it("HTTP upstream | span attributes", async function () { + + const res = await axios.get(`${host}/?foo=bar&x=42`, { + headers: { + "User-Agent": "otel-test", + "My-Header": "My-Value" + } + }); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + trace.resourceSpans.forEach((resourceSpan) => { + + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + validateResourceAttributes(resourceSpan.instrumentationLibrarySpans[0].spans[0].attributes, [ + { key: 'net.peer.ip', regex: /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ }, + { key: 'net.peer.port', fn: (actual) => actual > 0 }, + { key: 'http.method', value: 'GET' }, + { key: 'http.flavor', value: '1.1' }, + { key: 'http.target', value: '/?foo=bar&x=42' }, + { key: 'http.host', value: 'localhost:8000' }, + { key: 'http.server_name', value: 'otel_test' }, + { key: 'http.scheme', value: 'http' }, + { key: 'http.status_code', value: 200 }, + { key: 'http.user_agent', value: 'otel-test' }, + { key: 'http.request.header.host', value: undefined }, + { key: 'http.request.header.user_agent', value: undefined }, + { key: 'http.request.header.my_header', value: undefined }, + ]); + }); + + assert.equal(trace.resourceSpans[0].instrumentationLibrarySpans[0].spans[0].kind, "SPAN_KIND_CLIENT"); + assert.equal(trace.resourceSpans[0].instrumentationLibrarySpans[0].spans[0].name, "simple_backend"); + assert.equal(trace.resourceSpans[1].instrumentationLibrarySpans[0].spans[0].kind, "SPAN_KIND_SERVER"); + assert.equal(trace.resourceSpans[1].instrumentationLibrarySpans[0].spans[0].name, "simple_backend"); + }) + + it("location with opentelemetry_capture_headers on should capture headers", async function() { + + const res = await axios.get(`${host}/capture_headers`, { + headers: { + "Request-Header": "Request-Value" + } + }); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + // Check server span only + const resourceSpan = trace.resourceSpans[1]; + + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + validateResourceAttributes(resourceSpan.instrumentationLibrarySpans[0].spans[0].attributes, [ + { key: 'http.request.header.host', value: undefined }, + { key: 'http.request.header.user_agent', value: undefined }, + { key: 'http.request.header.request_header', value: ['Request-Value'] }, + { key: 'http.response.header.response_header', value: ['Response-Value'] }, + ]); + }); + + + it("location with opentelemetry_capture_headers and sensitive header name should redact header value", async function () { + + const res = await axios.get(`${host}/capture_headers_with_sensitive_header_name`, { + headers: { + "Request-Header": "Foo", + "Request-Token": "Token", + } + }); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + // Check server span only + const resourceSpan = trace.resourceSpans[1]; + + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + validateResourceAttributes(resourceSpan.instrumentationLibrarySpans[0].spans[0].attributes, [ + { key: 'http.request.header.host', value: undefined }, + { key: 'http.request.header.user_agent', value: undefined }, + { key: 'http.request.header.request_header', value: ['[REDACTED]'] }, + { key: 'http.response.header.response_header', value: ['[REDACTED]'] }, + { key: 'http.request.header.request_token', value: ['[REDACTED]'] }, + { key: 'http.response.header.response_token', value: ['[REDACTED]'] }, + ]); + }); + + it("location with opentelemetry_capture_headers and sensitive header value should redact header value", async function () { + + const res = await axios.get(`${host}/capture_headers_with_sensitive_header_value`, { + headers: { + "Bar": "Request-Value" + } + }); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + // Check server span only + const resourceSpan = trace.resourceSpans[1]; + + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + validateResourceAttributes(resourceSpan.instrumentationLibrarySpans[0].spans[0].attributes, [ + { key: 'http.request.header.host', value: undefined }, + { key: 'http.request.header.user_agent', value: undefined }, + { key: 'http.request.header.bar', value: ['[REDACTED]'] }, + { key: 'http.response.header.bar', value: ['[REDACTED]'] }, + ]); + }); + + it("location without operation name should use operation name from server", async function () { + + const res = await axios.get(`${host}/no_operation_name`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + trace.resourceSpans.forEach((resourceSpan) => { + + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].name, "otel_test"); + + }); + }); + + it("HTTP upstream | span is created when no traceparent exists", async function() { + + const res = await axios.get(`${host}`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + trace.resourceSpans.forEach((resourceSpan, idx) => { + + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + validateResourceAttributes(resourceSpan.instrumentationLibrarySpans[0].spans[0].attributes, [ + { key: 'http.status_code', value: 200 }, + ]); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].kind, idx === 0 ? "SPAN_KIND_CLIENT" : "SPAN_KIND_SERVER"); + if (idx === 1) { + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].parentSpanId, ""); + } + }); + }); + + it("HTTP upstream | span is associated with parent", async function() { + + const parentSpanId = "2a9d49c3e3b7c461" + const inputTraceId = "aad85b4f655feed4d594a01cfa6a1d62" + + const res = await axios.get(host, { + headers: { + "traceparent": `00-${inputTraceId}-${parentSpanId}-00` + } + }); + + assert.equal(res.status, 200); + + const parsedBody = res.data; + + const [prefix, traceId, spanId, suffix] = parsedBody.traceparent.split('-'); + + assert.equal(prefix, '00'); + assert.equal(traceId, inputTraceId); + assert.notEqual(spanId, parentSpanId); + assert.equal(suffix, '01'); + + const traces = await getTrace(traceStream); + + assert.equal(traces.resourceSpans.length, 2); + + const serverResourceSpan = traces.resourceSpans[1]; + + assert(serverResourceSpan.instrumentationLibrarySpans.length, 1); + assert(serverResourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(serverResourceSpan.instrumentationLibrarySpans[0].spans[0].traceId, inputTraceId); + assert.equal(serverResourceSpan.instrumentationLibrarySpans[0].spans[0].parentSpanId, parentSpanId); + + }); + + it("PHP-FPM upstream | span attributes", async function() { + + const res = await axios.get(`${host}/app.php`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + trace.resourceSpans.forEach((resourceSpan, idx) => { + + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + validateResourceAttributes(resourceSpan.instrumentationLibrarySpans[0].spans[0].attributes, [ + { key: 'net.peer.ip', regex: /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ }, + { key: 'net.peer.port', fn: (actual) => actual > 0 }, + { key: 'http.method', value: 'GET' }, + { key: 'http.flavor', value: '1.1' }, + { key: 'http.target', value: '/app.php' }, + { key: 'http.host', value: 'localhost:8000' }, + { key: 'http.server_name', value: 'otel_test' }, + { key: 'http.scheme', value: 'http' }, + { key: 'http.status_code', value: 200 }, + ]); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].kind, idx === 0 ? "SPAN_KIND_CLIENT" : "SPAN_KIND_SERVER"); + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].name, "php_fpm_backend"); + + }); + }); + + it("PHP-FPM upstream | span is associated with parent", async function() { + + const parentSpanId = "2a9d49c3e3b7c461" + const inputTraceId = "aad85b4f655feed4d594a01cfa6a1d62" + + const res = await axios.get(`${host}/app.php`, { + headers: { + "traceparent": `00-${inputTraceId}-${parentSpanId}-00` + } + }); + + assert.equal(res.status, 200); + + const parsedBody = res.data; + + const [prefix, traceId, spanId, suffix] = parsedBody.traceparent.split('-'); + + assert.equal(prefix, '00'); + assert.equal(traceId, inputTraceId); + assert.notEqual(spanId, parentSpanId); + assert.equal(suffix, '01'); + + const traces = await getTrace(traceStream); + + assert.equal(traces.resourceSpans.length, 2); + + const serverResourceSpan = traces.resourceSpans[1]; + + assert(serverResourceSpan.instrumentationLibrarySpans.length, 1); + assert(serverResourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(serverResourceSpan.instrumentationLibrarySpans[0].spans[0].traceId, inputTraceId); + assert.equal(serverResourceSpan.instrumentationLibrarySpans[0].spans[0].parentSpanId, parentSpanId); + }); + + it("HTTP upstream | test b3 injection", async function() { + + const res = await axios.get(`${host}/b3`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + trace.resourceSpans.forEach((resourceSpan, idx) => { + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].name, "test_b3"); + if (idx === 1) { + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].parentSpanId, ""); + } + }); + }); + + it("PHP-FPM upstream | test b3 injection", async function() { + + const res = await axios.get(`${host}/b3`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + trace.resourceSpans.forEach((resourceSpan, idx) => { + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].name, "test_b3"); + if (idx === 1) { + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].parentSpanId, ""); + } + }); + }); + + it("HTTP upstream | test b3 propagation", async function() { + + const parentSpanId = "2a9d49c3e3b7c461" + const inputTraceId = "aad85b4f655feed4d594a01cfa6a1d62" + + const res = await axios.get(`${host}/b3`, { + headers: { + "b3": `${inputTraceId}-${parentSpanId}-1` + } + }); + + assert.equal(res.status, 200); + + const parsedBody = res.data; + const [traceId, spanId] = parsedBody.b3.split('-'); + + assert.equal(traceId, inputTraceId); + assert.notEqual(spanId, parentSpanId); + + const traces = await getTrace(traceStream); + + assert.equal(traces.resourceSpans.length, 2); + + traces.resourceSpans.forEach((resourceSpan) => { + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].traceId, inputTraceId); + assert.notEqual(resourceSpan.instrumentationLibrarySpans[0].spans[0].spanId, parentSpanId); + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].name, "test_b3"); + }); + }); + + it("HTTP upstream | multiheader b3 propagation", async function() { + + const parentSpanId = "2a9d49c3e3b7c461" + const inputTraceId = "aad85b4f655feed4d594a01cfa6a1d62" + + const res = await axios.get(`${host}/b3`, { + headers: { + "X-B3-TraceId": inputTraceId, + "X-B3-SpanId": parentSpanId, + "X-B3-Sampled": "1" + } + }); + + assert.equal(res.status, 200); + + const parsedBody = res.data; + const [traceId, spanId] = parsedBody.b3.split('-'); + + assert.equal(traceId, inputTraceId); + assert.notEqual(spanId, parentSpanId); + + const traces = await getTrace(traceStream); + + assert.equal(traces.resourceSpans.length, 2); + + traces.resourceSpans.forEach((resourceSpan) => { + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].traceId, inputTraceId); + assert.notEqual(resourceSpan.instrumentationLibrarySpans[0].spans[0].spanId, parentSpanId); + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].name, "test_b3"); + }); + }); + + it("Accessing a file produces a span", async function() { + + const res = await axios.get(`${host}/files/content.txt`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(res.data, "Lorem Ipsum"); + + assert.equal(trace.resourceSpans.length, 2); + + trace.resourceSpans.forEach((resourceSpan, idx) => { + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].name, "file_access"); + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].kind, idx === 0 ? "SPAN_KIND_INTERNAL" : "SPAN_KIND_SERVER"); + + validateResourceAttributes(resourceSpan.instrumentationLibrarySpans[0].spans[0].attributes, [ + { key: 'http.method', value: 'GET' }, + { key: 'http.flavor', value: '1.1' }, + { key: 'http.target', value: '/files/content.txt' }, + { key: 'http.host', value: 'localhost:8000' }, + { key: 'http.server_name', value: 'otel_test' }, + { key: 'http.scheme', value: 'http' }, + { key: 'http.status_code', value: 200 }, + ]); + }); + }); + + it("Accessing a excluded uri produces no span", async function() { + + const res = await axios.get(`${host}/ignored.php`); + + assert.equal(res.status, 200); + + try { + await getTrace(traceStream, 1000); + assert.fail('expected timeout'); + } catch(ex) { + assert.equal(ex.message, 'timeout'); + } + }); + + it("Accessing a route with disabled OpenTelemetry does not produce spans nor propagate", async function() { + + const res = await axios.get(`${host}/off`); + + assert.equal(res.status, 200); + + try { + await getTrace(traceStream, 1000); + assert.fail('expected timeout'); + } catch (ex) { + assert.equal(ex.message, 'timeout'); + } + }); + + it("Accessing a route with disabled trustIncomingsSpans is not associated with a parent", async function() { + + const inputTraceId = "aad85b4f655feed4d594a01cfa6a1d62" + + const res = await axios.get(`${host}/distrust_incoming_spans`, { + headers: { + "traceparent": `00-${inputTraceId}-2a9d49c3e3b7c461-00` + } + }); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + const serverResourceSpan = trace.resourceSpans[1]; + assert.equal(serverResourceSpan.instrumentationLibrarySpans.length, 1); + assert.equal(serverResourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.notEqual(serverResourceSpan.instrumentationLibrarySpans[0].spans[0].traceId, inputTraceId); + assert.equal(serverResourceSpan.instrumentationLibrarySpans[0].spans[0].parentSpanId, ""); + }); + + it("Spans with custom attributes are produced", async function() { + + const res = await axios.get(`${host}/attrib`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + const serverResourceSpan = trace.resourceSpans[1]; + assert.equal(serverResourceSpan.instrumentationLibrarySpans.length, 1); + assert.equal(serverResourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + validateResourceAttributes(serverResourceSpan.instrumentationLibrarySpans[0].spans[0].attributes, [ + { key: 'test.attrib.custom', value: 'local' }, + { key: 'test.attrib.global', value: 'global' }, + { key: 'test.attrib.script', regex: /\d+\.\d+/ }, + ]); + }); + + it("Accessing trace context", async function() { + + const res = await axios.get(`${host}/context`); + + assert.equal(res.status, 200); + + const traces = await getTrace(traceStream); + + assert.equal(traces.resourceSpans.length, 2); + + const innerResourceSpan = traces.resourceSpans[0]; + + assert.equal(innerResourceSpan.instrumentationLibrarySpans.length, 1); + assert.equal(innerResourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + const span = innerResourceSpan.instrumentationLibrarySpans[0].spans[0]; + const traceId = span.traceId; + const spanId = span.spanId; + + const traceparent = res.headers['context-traceparent']; + assert(traceparent); + + assert.equal(traceparent, `00-${traceId}-${spanId}-01`); + }); + + it("Accessing trace id", async function() { + + const res = await axios.get(`${host}/trace_id`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + const serverResourceSpan = trace.resourceSpans[1]; + const span = serverResourceSpan.instrumentationLibrarySpans[0].spans[0]; + + const traceId = span.traceId; + + const traceIdHeader = res.headers['trace-id']; + assert(traceIdHeader); + + assert.equal(traceIdHeader, traceId); + }); + + it("Accessing span id", async function() { + + const res = await axios.get(`${host}/span_id`); + + assert.equal(res.status, 200); + + const trace = await getTrace(traceStream); + + assert.equal(trace.resourceSpans.length, 2); + + const serverResourceSpan = trace.resourceSpans[1]; + + assert.equal(serverResourceSpan.instrumentationLibrarySpans.length, 1); + assert.equal(serverResourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + const span = serverResourceSpan.instrumentationLibrarySpans[0].spans[0]; + + const spanId = span.spanId; + + const spanIdHeader = res.headers['span-id']; + assert(spanIdHeader); + + assert.equal(spanIdHeader, spanId); + }); + + it("Accessing 301 redirect does not crash", async function () { + + const res = await axios.get(`${host}/redirect_301`, { + maxRedirects: 0, + validateStatus: (status) => { + return status === 301; + } + }); + + assert.equal(res.status, 301); + + const headers = res.headers; + assert.equal(headers['location'], `${host}/redirect_301/`); + + await getTrace(traceStream); + }); + + it("Accessing internal request does not crash", async function () { + + const res = await axios.get(`${host}/route_to_internal`); + + assert.equal(res.status, 200); + + const headers = res.headers; + const traceId = headers['trace-id']; + + assert(traceId); + + const traces = await getTrace(traceStream); + + assert.equal(traces.resourceSpans.length, 2); + + traces.resourceSpans.forEach((resourceSpan) => { + assert(resourceSpan.instrumentationLibrarySpans.length, 1); + assert(resourceSpan.instrumentationLibrarySpans[0].spans.length, 1); + + assert.equal(resourceSpan.instrumentationLibrarySpans[0].spans[0].traceId, traceId); + }); + }); + +});