Skip to main content

Configure a custom telemetry provider for scopes

Extend nullplatform's observability by implementing custom log and metric providers for agent-backed scopes. This guide shows how to configure your provider and channel for any backend, so logs and performance metrics appear in the UI.

When to use this

Use a custom telemetry provider if nullplatform doesn't support your product (for example, Grafana) or if telemetry only exists in a private environment that nullplatform can't reach (for example, an Elasticsearch instance in your infrastructure).

To do this, you'll use the nullplatform agent, which lets you:

  1. Implement and run your own scripts to collect logs and metrics from any provider.
  2. Keep those providers private, because the agent runs in your infrastructure and can access them without exposing them externally.

Prerequisites

You'll need the following before configuring a custom telemetry provider:

Configure a custom telemetry provider

To use a custom telemetry provider with nullplatform, you need to configure two things:

  1. The telemetry provider for the scope types you want.
  2. A telemetry notification channel to tell nullplatform where and how to reach your provider.

1. Configure the telemetry provider for a scope type

In nullplatform, you can deploy different application types called scope types. Each scope type can use a different metrics provider.

When using your own provider, configure the platform so that metrics and logs come from an external provider for each scope type that should use it.

You can do this as follows:

np nrn patch \
--nrn "organization=1:account=2:namespace=3:application=4" \
--body "{
\"global.${SCOPE_TYPE_SLUG}_metric_provider\": \"externalmetrics\",
\"global.${SCOPE_TYPE_SLUG}_log_provider\": \"external\"
}"

Example configuration

For example, if you use the nullplatform "Containers" scope, the configuration looks like this:

np nrn patch \
--nrn "organization=1:account=2:namespace=3:application=4" \
--body "{
\"global.containers_metric_provider\": \"externalmetrics\",
\"global.containers_log_provider\": \"external\"
}"

2. Configure the channel

Once you tell nullplatform that metrics and logs come from your provider, you also need to create a channel to tell nullplatform where and how to reach your provider.

This notification channel will:

  • Consume "telemetry" notifications.
  • Use the nullplatform agent as the execution runtime.
  • Execute a script that points to your implementation.

Go to Platform settings > Notifications > Channels and click + New channel.

scope-telemetry-channel
note

The selectors you define here must match the tags assigned to your agent.

Example command configuration

Suppose your GitHub organization is acme-corp and you have a repo named telemetry that implements a provider named grafana. In that case, the script you configure in the channel would be:

/root/.np/nullplatform/scopes/entrypoint --service-path=/root/.np/acme-corp/telemetry/grafana

Required implementations for logs or metrics

You can implement logs, metrics, or both. Use the operations below based on what you need.

Logs

To implement a logs provider, define the following operations.

OperationPurposeWorkflow Path
Read logsRetrieve application logslog/workflows/log.yaml

Read logs

Retrieves application logs from your logging backend. This enables log viewing within the nullplatform UI.

Workflow path

<provider_path>/log/workflows/log.yaml

Input

The following JSON is provided via the NP_ACTION_CONTEXT environment variable:

{
"action": "log:read",
"entity_nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"arguments": {
"log_type": "application",
"application_id": "4",
"scope_id": "5",
"limit": 100,
"start_time": 1769019098258,
"end_time": 1769018602617,
"scope_type": "custom",
"scope_provider": "$scope-type-uuid"
},
"scope": {
"id": 5,
"slug": "my-scope",
"name": "my-scope",
"type": "custom",
"nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"provider": "$scope-type-uuid",
"profiles": [
"environment_development",
"country_argentina"
]
},
"service": {
"id": "9a4c1f2e-7b8d-4f6a-9c3e-1d2b8e5a6f40",
"dimensions": {
"environment": "development",
"country": "argentina"
}
},
"parameters": {
"scope_id": 5
},
"tags": {
"organization_id": "1",
"organization": "acme-corp",
"account_id": "2",
"account": "main",
"namespace_id": "3",
"namespace": "acme-namespace",
"application_id": "4",
"application": "acme-services-action-app",
"scope_id": "5",
"scope": "my-scope"
}
}

Response format

Your script must output a JSON object to stdout with the following structure:

{
"results": [
{
"datetime": "2024-01-15T10:30:00.000Z",
"message": "Application started successfully"
},
{
"timestamp": "2024-01-15T10:30:01.500Z",
"message": "Connected to database"
}
],
"next_page_token": "eyJwYdlIMDAsInQiOR9="
}
FieldTypeRequiredDescription
resultsarrayYesArray of log entries
results[].timestampstringYesISO 8601 formatted timestamp
results[].messagestringYesLog message content
next_page_tokenstringNoToken for pagination; omit if no more results

Metrics

To implement a metrics provider, define the following operations.

OperationPurposeWorkflow Path
List available metricsEnumerate available metricsmetric/workflows/list.yaml
Read metricFetch time-series metricsmetric/workflows/metric.yaml
List instancesList running instances for a scopeinstance/workflows/list.yaml

List available metrics

Lists the metrics available from your backend for discovery in the nullplatform UI.

Workflow path

<provider_path>/metric/workflows/list.yaml

Input

The following JSON is provided via the NP_ACTION_CONTEXT environment variable:

{
"action": "metric:list",
"entity_nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"arguments": {
"application_id": "4",
"scope_type": "custom",
"scope_provider": "$scope-type-uuid"
},
"scope": {
"id": 5,
"slug": "my-scope",
"name": "my-scope",
"type": "custom",
"nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"provider": "$scope-type-uuid",
"profiles": [
"environment_development",
"country_argentina"
]
},
"service": {
"id": "9a4c1f2e-7b8d-4f6a-9c3e-1d2b8e5a6f40",
"dimensions": {
"environment": "development",
"country": "argentina"
}
},
"parameters": {
"scope_id": 5
},
"tags": {
"organization_id": "1",
"organization": "acme-corp",
"account_id": "2",
"account": "main",
"namespace_id": "3",
"namespace": "acme-namespace",
"application_id": "4",
"application": "acme-services-action-app",
"scope_id": "5",
"scope": "my-scope"
}
}

Response format

{
"results": [
{
"name": "http.rpm",
"title": "Throughput",
"unit": "rpm",
"available_filters": ["scope_id", "instance_id"],
"available_group_by": ["instance_id"]
},
{
"name": "http.response_time",
"title": "Response Time",
"unit": "ms",
"available_filters": ["scope_id", "instance_id"],
"available_group_by": ["instance_id"]
},
{
"name": "http.error_rate",
"title": "Error Rate",
"unit": "%",
"available_filters": ["scope_id", "instance_id"],
"available_group_by": ["instance_id"]
},
{
"name": "system.cpu_usage_percentage",
"title": "CPU Usage",
"unit": "%",
"available_filters": ["scope_id", "instance_id"],
"available_group_by": ["instance_id"]
},
{
"name": "system.memory_usage_percentage",
"title": "Memory Usage",
"unit": "%",
"available_filters": ["scope_id", "instance_id"],
"available_group_by": ["scope_id", "instance_id"]
}
]
}
FieldTypeRequiredDescription
resultsarrayYesArray of available metrics
results[].namestringYesMetric identifier used in API calls
results[].titlestringYesHuman-readable display name
results[].unitstringYesUnit of measurement for display
results[].available_filtersarrayYesSupported filter dimensions
results[].available_group_byarrayYesSupported grouping dimensions

Read metric

Fetches time-series metric data for visualization and alerting in nullplatform.

Workflow path

<provider_path>/metric/workflows/metric.yaml

Input

The following JSON is provided via the NP_ACTION_CONTEXT environment variable:

{
"action": "metric:data",
"entity_nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"arguments": {
"metric": "http.error_rate",
"start_time": "2026-01-21T18:26:49.689Z",
"end_time": "2026-01-21T18:56:49.689Z",
"filters": {
"scope": "my-scope"
},
"period": 60,
"group_by": [],
"application_id": 4,
"scope_id": "5",
"options": {},
"scope_type": "custom",
"scope_provider": "$scope-type-uuid"
},
"scope": {
"id": 5,
"slug": "my-scope",
"name": "my-scope",
"type": "custom",
"nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"provider": "$scope-type-uuid",
"profiles": [
"environment_development",
"country_argentina"
]
},
"service": {
"id": "9a4c1f2e-7b8d-4f6a-9c3e-1d2b8e5a6f40",
"dimensions": {
"environment": "development",
"country": "argentina"
}
},
"parameters": {
"scope_id": 5
},
"tags": {
"organization_id": "1",
"organization": "acme-corp",
"account_id": "2",
"account": "main",
"namespace_id": "3",
"namespace": "acme-namespace",
"application_id": "4",
"application": "acme-services-action-app",
"scope_id": "5",
"scope": "my-scope"
}
}

Supported metric names

The supported metric names are the ones you defined in your metric list implementation.

Response format

{
"metric": "http.rpm",
"type": "gauge",
"period_in_seconds": 60,
"unit": "count_per_minute",
"results": [
{
"selector": {
"scope_id": "456",
"instance_id": "pod-abc123"
},
"data": [
{
"timestamp": "2024-01-15T10:00:00Z",
"value": 1250.5
},
{
"timestamp": "2024-01-15T10:01:00Z",
"value": 1340.2
}
]
}
]
}
FieldTypeRequiredDescription
metricstringYesThe metric name
typestringYesMetric type (gauge, counter)
period_in_secondsnumberYesData point aggregation period
unitstringYesUnit of measurement
resultsarrayYesArray of time series
results[].selectorobjectYesLabels identifying the series
results[].dataarrayYesArray of data points
results[].data[].timestampstringYesISO 8601 timestamp
results[].data[].valuenumberYesMetric value

List instances

Lists running instances (pods, containers, VMs) for a scope so you can filter metrics per instance in nullplatform.

Workflow path

<provider_path>/instance/workflows/list.yaml

Input

The following JSON is provided via the NP_ACTION_CONTEXT environment variable:

{
"action": "instance:data",
"entity_nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"arguments": {
"application_id": [
"4"
],
"scope_id": [
"5"
],
"scope_type": "custom",
"scope_provider": "$scope-type-uuid"
},
"scope": {
"id": 5,
"slug": "my-scope",
"name": "my-scope",
"type": "custom",
"nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"provider": "$scope-type-uuid",
"profiles": [
"environment_development",
"country_argentina"
]
},
"service": {
"id": "9a4c1f2e-7b8d-4f6a-9c3e-1d2b8e5a6f40",
"dimensions": {
"environment": "development",
"country": "argentina"
}
},
"parameters": {
"scope_id": 5
},
"tags": {
"organization_id": "1",
"organization": "acme-corp",
"account_id": "2",
"account": "main",
"namespace_id": "3",
"namespace": "acme-namespace",
"application_id": "4",
"application": "acme-services-action-app",
"scope_id": "5",
"scope": "my-scope"
}
}

Response format

{
"results": [
{
"id": "pod-abc123-xyz",
"selector": {
"application_id": "123",
"scope_id": "456",
"deployment_id": "789"
},
"details": {
"namespace": "production",
"ip": "10.0.1.50",
"dns": "10.0.1.50.production.pod.cluster.local",
"cpu": {
"requested": 0.25,
"limit": 0.5
},
"memory": {
"requested": "256Mi",
"limit": "512Mi"
},
"architecture": "x86"
},
"state": "Running",
"launch_time": "2024-01-15T08:00:00Z",
"spot": false
}
]
}
FieldTypeRequiredDescription
resultsarrayYesArray of instance objects
results[].idstringYesUnique instance identifier
results[].selectorobjectYesLabels/metadata for filtering
results[].detailsobjectYesInstance details
results[].details.namespacestringNoDeployment namespace
results[].details.ipstringNoInstance IP address
results[].details.dnsstringNoInstance DNS name
results[].details.cpuobjectNoCPU resource allocation
results[].details.cpu.requestednumberNoRequested CPU cores
results[].details.cpu.limitnumberNoCPU limit in cores
results[].details.memoryobjectNoMemory resource allocation
results[].details.memory.requestedstringNoRequested memory
results[].details.memory.limitstringNoMemory limit
results[].details.architecturestringNoCPU architecture (x86, arm64)
results[].statestringYesInstance state (Running, Pending, etc.)
results[].launch_timestringYesISO 8601 launch timestamp
results[].spotbooleanNoWhether instance is a spot/preemptible instance

Best practices

  • Error Handling: Always return valid JSON, even on errors. Use the empty results pattern:

    {"metric":"","type":"","period_in_seconds":0,"unit":"","results":[]}
  • Pagination: Implement pagination for log endpoints to handle large result sets efficiently.

  • Logging: Logs and metrics are read by the agent from standard output, so don't print logs while processing telemetry. That would break the JSON format the agent sends to the nullplatform API.