How Do You Build a Custom Receiver in OpenTelemetry?

How Do You Build a Custom Receiver in OpenTelemetry?

A custom receiver teaches the OpenTelemetry Collector to listen to a data source it does not yet understand. This guide walks through the ideas, the building blocks, the design choices and the traps, from first plan to production release.

Imagine a translator who works at a busy international airport. Announcements arrive in many languages: some spoken over loudspeakers, some printed on boards, some handed over on paper slips. The airport has a standard way of recording every announcement in one common form, so that staff anywhere can read it. When a new airline arrives that uses a language nobody knows, the airport hires a new translator whose only job is to listen to that airline and write down its announcements in the common form. A custom receiver is that translator. It listens to a source the Collector does not yet understand, and it turns what it hears into the common language of OpenTelemetry.

1Why Custom Receivers Exist

Before building one, it helps to understand when building one is the right answer.

The world is full of formats

Organisations produce telemetry in countless shapes. A legacy application writes status lines to a file. A hardware device exposes numbers through a proprietary protocol. A database publishes statistics through a special query. A message queue carries events in a company-specific layout. A vendor product offers a web interface that returns readings in its own structure. None of these speak the standard OpenTelemetry protocol, and none of them will change just because you would like them to.

The OpenTelemetry Collector already ships with a large family of receivers that cover popular sources, and these should always be your first stop. But no collection of ready-made receivers can cover every system in every company. When the source you care about is unusual, private, or too new, you have two honest choices. You can modify the source so that it speaks a known protocol, which is often impossible. Or you can write a receiver that bridges the gap.

Simple Analogy

Think of travel adapters for electrical plugs. Most countries are covered by a handful of standard adapters you can buy in a shop. If you visit a country with an unusual socket, you either rewire the wall, which is rarely possible, or you build a small adapter. A custom receiver is a small adapter between a strange socket and the standard plug.

Reasons that justify the effort

Reason 1

Proprietary Sources

Internal systems with private formats that no public receiver will ever support.

Reason 2

Legacy Systems

Old software that cannot be changed but still holds valuable operating data.

Reason 3

Hardware and Devices

Equipment that exposes readings through specialised protocols or interfaces.

Reason 4

Business Metrics

Numbers held in business systems, such as order counts or queue lengths, that teams want beside technical telemetry.

!
Check First

Before writing anything, search the list of existing receivers and the contributed components. A receiver that already does ninety percent of the job is better to extend or configure than a new one to maintain for years.

The cost of owning a receiver

Writing the first version is the cheap part. A receiver is software that must be kept alive for years. The source will release new versions that change its output. The Collector libraries will change their programming interfaces. Security problems will be found in dependencies. Users will ask questions and report problems. Every receiver you create is a promise to do this work, so it is worth asking whether the benefit justifies the promise.

A useful test is to imagine the receiver three years from now. Who will maintain it? Who will know how it works when its author has moved to another team? Which tests will tell them they have broken something? If you cannot answer these questions, invest in documentation and tests before adding more features, or consider whether another approach might serve better.

OptionEffort nowEffort over timeFits when
Use an existing receiver as isVery lowVery lowA public receiver already covers your source
Change the source to speak a standard protocolMedium to highLowYou own the source and can modify it
Add a small translation layer outside the CollectorMediumMediumThe format is simple and you prefer not to maintain Collector code
Build a custom receiverMedium to highMedium to highThe source is unusual, cannot be changed, and matters enough

2Core Concepts: The Collector in Plain Words

A receiver is only one piece of a larger machine, so we should see the whole machine first.

The OpenTelemetry Collector is a program that receives telemetry, processes it, and sends it onwards. It is assembled from components of four main kinds. Receivers bring data in. Processors change data on its way through, for example by adding labels or dropping unwanted items. Exporters send data out to another destination. Connectors join one pipeline to another. Extensions provide helpful services that sit beside the pipelines, such as authentication, health checks and storage.

These components are wired together in pipelines, and each pipeline handles one kind of signal: traces, metrics or logs. A receiver feeds the start of a pipeline, processors act in the middle, and exporters finish it. The configuration file describes which components exist and how the pipelines connect them.

flowchart LR
  S1[Your data source] --> R[Custom receiver]
  S2[Other sources] --> R2[Existing receivers]
  R --> P[Processors]
  R2 --> P
  P --> E[Exporters]
  E --> D[Storage or vendor]
        
FIG 1 — Where a custom receiver sits inside a Collector pipeline
Term 1

Receiver

The component that brings telemetry into the Collector, either by listening or by fetching it.

Term 2

Signal

A kind of telemetry: traces, metrics or logs. A receiver may support one or several.

Term 3

Pipeline

A chain of receivers, processors and exporters for one signal.

Term 4

Consumer

The next step that accepts data from a receiver. A receiver hands its data to a consumer and does not care what happens afterwards.

Term 5

Factory

The object that knows how to create a receiver and its default settings. Every component type has one.

Term 6

pdata

The Collector’s internal data model for telemetry, used to build traces, metrics and logs in memory.

The contract in one sentence

A receiver’s whole contract can be stated simply: it obtains data from somewhere, converts it into the internal data model, and passes it to the next consumer in the pipeline. Everything else in this guide is detail about doing those three things well.

Where data goes after the receiver

It helps to picture what happens after your receiver hands over its data. The next consumer may be a processor that adds labels, groups items into batches, limits memory use or filters unwanted data. Later steps may send the data to several destinations at once. Your receiver does not know or control any of this. That separation is deliberate: it lets the same receiver feed very different pipelines in different companies.

It also means your receiver must not make assumptions about what follows. It should not, for example, assume that a batching step exists, or that a particular label will be added later. It produces complete, self-describing data and trusts the rest of the pipeline to do its own job.

Receivers, connectors and extensions: how they differ

Newcomers sometimes confuse the building blocks. A receiver is the entry point from the outside world. A connector sits between two pipelines and acts as an exporter at the end of one and a receiver at the start of the next; it is used for tasks such as turning traces into metrics. An extension does not touch the data at all; it provides services such as authentication or health checks. If your task is to bring new outside data in, you need a receiver. If your task is to transform data already inside, you need a processor or a connector.

Block 1

Receiver

Brings data in from the outside world into a pipeline.

Block 2

Processor

Changes, filters or groups data already inside a pipeline.

Block 3

Exporter

Sends data from a pipeline to an outside destination.

Block 4

Connector

Joins two pipelines, often converting one kind of signal into another.

Block 5

Extension

Provides side services such as authentication, storage and health checks.

3Two Families: Push Receivers and Pull Receivers

The first design decision is whether your receiver waits for data or goes to fetch it.

Push receivers

A push receiver opens a door and waits. Applications or devices send data to it whenever they have something to say. The receiver listens on a network port, a file, a queue or some other channel, and handles each arrival as it comes. The timing is controlled by the senders, so the receiver must be ready for bursts and quiet spells alike.

Pull receivers

A pull receiver, sometimes called a scraper, goes out on a schedule and fetches data. Every ten seconds or every minute it asks the source for its current readings, converts the answer, and passes it on. The timing is controlled by the receiver, which makes the load predictable and easy to reason about.

QuestionPush receiverPull receiver
Who decides when data moves?The sourceThe receiver, by schedule
Typical sourcesApplications that emit events, agents, devices that send readingsDatabases, status pages, management interfaces, files read at intervals
Main design worryBursts, bad senders, backpressureSlow sources, timeouts, repeated readings
Common signalsTraces and logsMetrics
Helper supportServer settings for network listenersScraper helpers that run schedules for you
Simple Analogy

A push receiver is like a letterbox: letters arrive whenever the postman comes, and you must be ready for a big pile after a holiday. A pull receiver is like checking your letterbox yourself every morning: you decide when to look, and the pile is whatever has built up.

i
Choosing

Let the source decide. If the source can send data by itself, a push receiver is natural. If the source only answers when asked, build a pull receiver. Some sources can do both, and then the choice depends on which is easier to run safely.

Some receivers combine both families. For example, one might listen for event notifications and also poll for a periodic summary. Such combinations are possible, but they double the number of behaviours to test, so it is usually cleaner to build two separate receivers that share a library for parsing.

Hybrid sources and tricky cases

Real sources are rarely tidy. A file that grows over time is neither purely pushed nor purely pulled: the receiver watches the file and reads new lines as they appear, remembering how far it has read. A queue behaves like a push source from the receiver’s point of view, but the receiver actually asks the queue for messages and must confirm each one after handling it. A web interface that offers both a summary page and a stream of events invites two separate receivers.

For sources that must remember a position, such as a file offset or a queue position, think about what should happen when the Collector restarts. If the position is lost, the receiver may re-read old data and create duplicates, or skip data that arrived while it was down. The Collector offers storage extensions that let a receiver keep such positions across restarts, and using them is usually better than inventing a private file.

!
Delivery Guarantees

Decide what promise your receiver makes: at most once, meaning data may be lost but is never duplicated, or at least once, meaning data is never lost but may be duplicated. Neither is wrong, but users must be told which one they get, because it affects how they build alerts and reports on top of the data.

Source styleFamilySpecial concern
Growing log fileWatcher, behaves like pushRemembering read position and handling rotation
Message queueConsumer, behaves like pushAcknowledging messages only after safe handoff
Status pagePullParsing a format that may change between versions
Event stream over a networkPush or long-lived pullReconnecting after drops without duplicating data

4Architecture: The Parts of a Receiver

Every receiver, however different its source, is built from the same small set of parts.

Receivers for the Collector are written in the Go programming language, because the Collector itself is written in Go. A receiver is a small Go package that provides a few well-defined pieces, and the Collector assembles them at start-up. Exact names of types and functions change between versions of the Collector libraries, so always confirm them against the version you build with. The ideas, however, remain stable.

Part 1

Type Name

A short, unique name that identifies the receiver in configuration files, for example the word you write on the line that declares it.

Part 2

Configuration Structure

A structure describing the settings a user may supply, such as addresses, credentials, intervals and options.

Part 3

Default Configuration

A function that returns sensible starting values, so a user needs to write only what differs.

Part 4

Factory

The object that ties the name, the configuration and the creating functions together, and declares which signals are supported and how stable they are.

Part 5

Receiver Instance

The running object, with a start action and a shutdown action, that does the real work of obtaining and converting data.

Part 6

Next Consumer

The handle through which the receiver passes finished data into the pipeline.

flowchart TD
  U[User configuration file] --> F[Factory]
  F --> C[Default configuration merged with user settings]
  C --> V[Validation]
  V --> R[Receiver instance created]
  R --> S[Start]
  S --> W[Obtain and convert data]
  W --> N[Pass to next consumer]
  S -.-> X[Shutdown when Collector stops]
        
FIG 2 — How a receiver is created, started and used

Metadata describes the receiver

Most mature receivers also carry a small metadata file that describes what they are: their name, their stability level, the metrics they produce, and the attributes those metrics carry. A code generator reads this file and produces repetitive code, documentation and tests. Using it keeps the receiver consistent with the rest of the ecosystem and removes a large amount of tedious writing.

Simple Analogy

The factory is like a shop’s order form. The customer fills in only the lines they care about, the shop supplies standard values for the rest, checks the form for mistakes, and then builds the product. The product, the receiver, is handed over only when the form passes inspection.

5Planning Before You Write

Most failed receivers fail in the planning, not in the code.

Study the source properly

Start by learning everything about how the source exposes data. Does it send or must it be asked? What formats does it use? How often does it change? Is there a limit on how many requests it can serve? What happens when it is unavailable? Does it require a login, and how does that login expire? Each of these questions will shape a decision in the receiver, and discovering the answers after the code is written is expensive.

Decide what the data means

Next, decide how source data maps to OpenTelemetry concepts. A reading that goes up and down, such as current temperature, maps to one kind of metric. A count that only increases, such as total requests served, maps to another. A record of something that happened at a moment in time maps to a log. A request that travels through several steps maps to a trace made of spans. Choosing the right concept is the most important modelling choice, because it decides how backends will draw and store the data.

Source behaviourBest-fitting conceptReason
A value that rises and falls, such as a queue lengthGauge metricIt describes the current level
A total that only grows, such as bytes sentSum metric, monotonicBackends can compute rates from it
A spread of measurements, such as response timesHistogram metricIt keeps the shape of the distribution
A single event with detailsLog recordIt carries text and attributes at one moment
A journey across steps with timingTrace of spansIt shows parent and child relationships and durations

Follow shared naming conventions

OpenTelemetry publishes semantic conventions, which are agreed names for common attributes and metrics, such as the names for a host, a service or a database system. Using them means that dashboards and alerts written for other sources will work for yours as well. Invent new names only for things that are truly unique to your source, and prefix them clearly so they cannot clash with future standard names.

1

List the source’s data

Write down every reading, event and relationship the source can provide, and note how each is obtained.

2

Choose what to collect

Select the subset that people will really use. Collecting everything makes the receiver heavy and the output noisy.

3

Map each item to a concept

Decide whether each item is a gauge, a sum, a histogram, a log or a span, and record the reasoning.

4

Name things using conventions

Reuse standard names and units wherever they exist, and document any new ones.

5

Decide push or pull

Select the family of receiver that suits the source and fix the schedule or listening method.

Write down the contract

Before coding, write a short document that states what the receiver will do. It should list the data it will collect, the names, types and units of each item, the settings it will accept, the failure behaviour it promises and the versions of the source it supports. This contract is useful in three ways. It forces you to think through gaps. It lets reviewers challenge decisions before they are expensive to change. And it becomes the seed of the user documentation.

Know the source’s limits

Every source has limits that a receiver must respect. A database may slow down if queried too often. A web interface may block clients that send too many requests. A device may accept only one connection at a time. Learn these limits during planning and design the receiver so that it stays well inside them by default. A receiver that causes trouble for its source will be switched off, however clever it is.

1

Ask the source’s owners

Find out how much extra load is acceptable and which times are sensitive.

2

Set safe defaults

Choose intervals, concurrency and request sizes that stay well below the limits.

3

Make limits visible in documentation

Explain which settings affect load on the source and what values are risky.

Supported Versions

Sources change over time, and their output formats may differ between versions. State which versions you have tested, detect the version at run time where possible, and fail clearly for versions you do not support instead of producing misleading data.

6The Toolchain: What You Need Installed and Why

A small number of tools make the work smooth, and each has a clear job.

Tool 1

The Go Language

Receivers are Go packages. You need a recent Go toolchain, of a version supported by the Collector libraries you choose.

Tool 2

The Collector Builder

A tool, often called ocb, that assembles a custom Collector distribution from a manifest listing the components you want, including your own.

Tool 3

The Metadata Generator

A code generator that reads a metadata file and produces the boilerplate for metrics, attributes, configuration and documentation.

Tool 4

Test Helpers

Libraries that give you ready-made fake consumers, fake hosts and settings, so you can test a receiver without running a whole Collector.

How the builder fits in

A standard Collector binary contains only the components someone chose to include. Your custom receiver will not be inside any downloaded binary, so you need to build your own. The builder reads a short manifest, which lists the Collector version, the output name, and the components to include by their module paths. For your own receiver, the manifest can point at a local folder during development and at a published module later. The builder then generates and compiles a complete Collector that contains your receiver beside the standard ones.

flowchart LR
  M[Builder manifest] --> B[Collector builder]
  Y[Your receiver module] --> B
  O[Other chosen components] --> B
  B --> G[Generated project]
  G --> K[Compiled custom Collector]
        
FIG 3 — Producing a Collector that contains your receiver

A sensible project layout

Keep the receiver in its own module with a clear folder structure: one place for the factory and configuration, one for the receiver logic, one for the translation code that converts source data, and one for tests and sample data. Keeping translation separate from transport is especially valuable. It lets you test the conversion with plain sample files, without any network or timing involved, and it lets you reuse the conversion if you later add a second way of reaching the same source.

i
Version Discipline

The Collector libraries are released together and evolve quickly. Pin the versions in your module, upgrade them on purpose, and read the release notes. Mixing versions of different Collector libraries in one project is a common source of confusing build errors.

Working locally and iterating quickly

A comfortable development routine saves hours. Keep a small local Collector configuration that uses your receiver and sends its output to a simple destination that prints data to the screen or a file. Rebuild with the builder after each change, run it against a test instance of the source, and look at the output directly. Seeing real output after every small change catches modelling mistakes long before they reach a dashboard.

Use the Collector’s own debugging exporter, which prints received data in readable form, to inspect what your receiver produces. Check that resource attributes are where you expect, that units appear, and that timestamps are sensible. When something looks wrong, this view is far quicker than reading code.

Step 1

Small Test Configuration

A minimal file with your receiver and a printing destination, so you see output immediately.

Step 2

Test Instance of the Source

A local or containerised copy of the source with safe sample data, so mistakes harm nothing.

Step 3

Rebuild and Run

Use the builder after each change and run the generated Collector with the test configuration.

Step 4

Inspect Output

Read what the receiver produced, checking names, units, attributes and timestamps.

i
Learn From Neighbours

Read the source of a few existing receivers of the same family before writing yours. Seeing how mature receivers organise their folders, settings and tests gives you a pattern to follow and a list of problems they had to solve.

7Designing the Configuration

The configuration is the face your receiver shows to its users, so it deserves care.

Make the common case easy

A good receiver works with almost no settings. The address of the source and perhaps a credential should be enough to get useful data flowing. Everything else should have a sensible default. Users judge a receiver by how quickly they get their first result, and a long list of required settings is the fastest way to lose them.

Reuse standard setting groups

The Collector provides ready-made groups of settings for common needs: network listening, encrypted connections, client connections, timeouts, retries and authentication. Embedding these groups in your configuration means your receiver behaves exactly like others, and users already know how to fill them in. It also saves you from writing and testing the same logic again, and from the subtle errors that come with it.

Group 1

Network Server Settings

For push receivers that listen on a port, including address, encryption and limits on message size.

Group 2

Client Settings

For pull receivers that connect out to a source, including endpoint, encryption, headers and timeouts.

Group 3

Schedule Settings

For scrapers, covering how often to collect, how long to wait before the first collection, and how long each collection may take.

Group 4

Authentication Settings

Links to authentication extensions, so credentials and checks are handled in one trusted place.

Validate early and clearly

A receiver can provide a validation step that runs when the Collector starts, before any data flows. Use it to reject impossible settings: a missing address, a negative interval, two options that cannot be used together. The error message should say which setting is wrong and what is expected. A message such as “invalid configuration” wastes time. A message that names the field and the rule saves a support request.

Setting typeGood practicePoor practice
IntervalsDefault to a reasonable value and reject zero or negative valuesRequire the user to guess a number
CredentialsAllow reference to secrets from files or environment variablesEncourage typing secrets into the file
EndpointsValidate format and give a clear errorAccept anything and fail later
Optional featuresOff by default, documented, with clear namesOn by default, causing surprising load
!
Change With Care

Once users depend on a setting name, renaming or removing it breaks their deployments. Treat configuration as a public interface, and follow a deprecation period before any breaking change.

8The Factory and the Lifecycle

A receiver has a clear life: it is created, started, runs for a long time, and is shut down.

Creation

The factory declares which signals the receiver supports and how stable each is. For each supported signal it provides a function that creates a receiver given the settings and the next consumer. A single receiver may support several signals, in which case the Collector may create a shared instance when the same receiver is used in several pipelines, so that two pipelines do not open the same port twice. When you design your receiver, think about this sharing, and make sure your code behaves correctly when several consumers are attached to one running receiver.

Start

The start action is called once when the Collector begins. Its job is to begin work and return promptly. It must not block forever, because the Collector waits for it before starting other components. Long-running work, such as listening or polling, should be launched in the background, and start should return as soon as that background work is ready.

The start action receives a context and a host. The host gives access to extensions, which is how a receiver finds its authentication or storage helpers. A common and important rule is that the context given to start is meant for the start action itself and should not be kept for long-running work, because it may be cancelled once start returns. Background work should create its own context that is cancelled by the shutdown action.

Shutdown

The shutdown action is called when the Collector stops. It must stop the background work, close connections, release resources and wait for in-flight handling to finish, within the time allowed. A receiver that ignores shutdown leaves threads running, ports open and data half-processed, which is disastrous during rolling upgrades.

1

Create

The factory builds the receiver from validated settings and the next consumer.

2

Start

The receiver opens its listener or launches its schedule, then returns quickly.

3

Run

Data is obtained, converted and passed on, for as long as the Collector runs.

4

Shut down

The receiver stops accepting work, finishes what is in progress, and releases everything it holds.

!
Common Mistake

Starting a background loop inside start, but forgetting to stop it in shutdown. The Collector appears to stop, yet the process lingers or leaks resources, and the problem only shows up in production restarts.

9Translating Source Data into the OpenTelemetry Model

This is the heart of the receiver, and the place where quality is won or lost.

The shape of the internal model

The internal model groups telemetry in layers. At the top is a resource, which describes the thing that produced the data, such as a host, a service or a device. Inside a resource is a scope, which describes the library or component that produced the telemetry. Inside the scope are the actual items: metric points, log records or spans. Each item carries attributes, which are labels that give it context.

flowchart TD
  RES[Resource: who produced this] --> SC[Scope: which component produced it]
  SC --> M[Metrics with data points]
  SC --> L[Log records]
  SC --> T[Spans]
  M --> A1[Attributes]
  L --> A2[Attributes]
  T --> A3[Attributes]
        
FIG 4 — Layers of the internal telemetry model

Put identity at the resource level

Facts that stay the same for everything a source produces, such as the name of a device, the cluster it belongs to or the version of its software, belong on the resource. Putting them on each data point wastes memory and makes data harder to group. Facts that vary from point to point, such as which disk or which queue a reading refers to, belong in attributes of the data point.

Metric details that are easy to get wrong

Detail 1

Units

Record the unit of every metric, using agreed unit names. A value of “5” is meaningless without knowing whether it means seconds, bytes or percent.

Detail 2

Timestamps

Every data point carries a time. Use the time the reading was taken, not the time it was converted, whenever the source provides it.

Detail 3

Start Time for Totals

Cumulative sums need a start time that marks when counting began, so backends can detect resets and compute rates correctly.

Detail 4

Temporality

A sum can report running totals or changes since the last report. Pick the one that matches what the source gives you, and state it.

!
Cardinality Alert

Do not turn unbounded values, such as user identifiers or request paths with random parts, into metric attributes. Each distinct value creates a new series, and backends become slow and expensive. Keep attribute values to a small, bounded set.

Logs and traces

For logs, fill the body with the message, set the severity from the source’s level, and use the source’s own time as the timestamp. Keep the original text intact so nothing is lost, and add parsed fields as attributes. For traces, the source must provide identifiers that link steps into a tree. Without reliable parent and child links, spans form a pile of separate dots instead of a story. If your source cannot provide this, a receiver for traces is the wrong tool, and logs or metrics are the honest choice.

Choosing metric types in practice

Mistakes in choosing metric types are the most common modelling error. Consider a source that reports “requests handled” as a number that grows forever. If you report it as a gauge, a backend will draw a line that rises endlessly and cannot tell a restart from a drop. If you report it as a monotonic cumulative sum, the backend can compute a rate per second and detect resets. The same number, reported with the wrong type, gives misleading charts.

Now consider a source that reports “current connections”. This rises and falls, so a gauge is correct. Reporting it as a sum would suggest that connections accumulate, which is not what the number means. A good habit is to ask: if I added up two readings taken a minute apart, would the result make sense? If yes, it is probably a sum. If no, it is probably a gauge.

Source saysQuestion to askType
Total bytes sent since startDoes it only grow, apart from restarts?Cumulative monotonic sum
Current temperatureDoes it move up and down?Gauge
Items waiting in a queueDoes adding two readings make sense?Gauge, not a sum
Response time distributionDoes the spread matter, not just the average?Histogram
Items processed in the last intervalIs it a change since the last report?Delta sum

Handling missing and partial data

Sources sometimes omit fields, return placeholder values or report that a reading is not available. Do not turn missing data into zero. A zero is a real reading that means “nothing”. A missing value means “unknown”, and the honest response is to leave that data point out. Replacing unknown with zero creates false calm in charts and can trigger or hide alerts incorrectly.

!
Zero Is Not Unknown

Treat missing values as absent, never as zero. A chart that shows a gap is telling the truth. A chart that shows zero is making something up.

Time zones and clocks

Sources may report times in local time zones, in different formats or with clocks that drift. Convert every timestamp into a single standard form, and be cautious when the source’s clock differs noticeably from the Collector’s. If you cannot trust the source’s time, use the time of collection and document that choice.

10Building a Pull Receiver (Scraper)

Pull receivers are the simplest to run safely, and the Collector helps you build them.

The Collector provides helper libraries for scrapers. You supply a scraper whose single job is to collect one round of readings from the source and return them in the internal model. The helper takes care of the schedule: it runs your scraper at the configured interval, enforces a time limit, applies an optional initial delay, and passes the results to the consumer. This division leaves you with only the part that is unique to your source.

sequenceDiagram
  participant H as Scraper helper
  participant S as Your scraper
  participant X as Source system
  participant C as Next consumer
  loop Every collection interval
    H->>S: Collect now, with a time limit
    S->>X: Ask for current readings
    X-->>S: Readings
    S->>S: Convert to metrics
    S-->>H: Metrics
    H->>C: Pass metrics on
  end
        
FIG 5 — The collection loop of a scraper-based receiver

Rules for a well-behaved scraper

1

Respect the time limit

If the source is slow, give up when the time is up and report the failure. Never let one slow round block the next.

2

Be gentle on the source

Ask for what you need in as few requests as possible, since a scraper that overloads a fragile source causes the very problems it was meant to detect.

3

Return partial results

If one of ten readings fails, return the nine that worked and report the one failure, rather than discarding everything.

4

Reuse connections

Open a connection once in start and reuse it between rounds, instead of reconnecting every time.

5

Make each round independent

Do not depend on hidden state from the previous round, except where a calculation such as a rate genuinely requires it.

Choosing the interval

The collection interval trades freshness against cost. A short interval gives more detail but loads both the source and the pipeline. A long interval is cheap but can miss short events. Pick a default that suits most users, such as a value in the range of tens of seconds to a minute, allow users to change it, and avoid intervals shorter than the source can sensibly answer.

Derived Values

If the source reports a running total but users want a rate, report the total as a cumulative sum and let the backend compute the rate. Computing rates inside the receiver forces you to remember earlier readings, and it breaks when the Collector restarts.

11Building a Push Receiver (Listener)

Push receivers meet the outside world directly, so they need more defensive thinking.

Opening the door

A push receiver starts a listener in its start action. For network protocols, use the ready-made server settings for the common web protocols, which handle addresses, encryption, size limits and authentication in a consistent way. For other channels, such as files, queues or special devices, use the appropriate client library and follow the same pattern: set up in start, run in the background, clean up in shutdown.

Handling each arrival

When data arrives, the receiver decodes it, converts it to the internal model, and hands it to the consumer. Decoding must be careful. Never trust the format of incoming data. Check that fields exist, that numbers are in range, that text is a sensible size, and that nesting is not absurdly deep. A single malformed message must never crash the receiver or corrupt other data.

Defence 1

Size Limits

Cap the size of each message so a sender cannot exhaust memory with one enormous request.

Defence 2

Concurrency Limits

Limit the number of simultaneous requests, so a burst cannot overwhelm the Collector.

Defence 3

Timeouts

Close connections that stay idle or send data too slowly, to stop slow senders from holding resources.

Defence 4

Authentication

Require senders to prove who they are, using the Collector’s authentication extensions rather than home-made checks.

Backpressure: telling senders to slow down

If the pipeline cannot keep up, the consumer returns an error or blocks. A well-behaved push receiver passes this message back to the sender, for example by returning a response that asks the sender to retry later. A receiver that silently drops data, or that buffers without limit, hides the problem and makes it worse. Honest signalling lets senders slow down, queue, or switch to a backup.

SituationGood responsePoor response
Pipeline is slowTell the sender to retry laterBuffer without limit until memory runs out
Message is malformedReject it with a clear reason and carry onCrash, or accept it and produce nonsense
Sender is unknownRefuse using the authentication extensionAccept everything from anywhere
Message is hugeRefuse it against a configured limitTry to load it all into memory

Reading data in streams

Some sources send very large messages or continuous streams. Reading such data a piece at a time, instead of all at once, keeps memory use steady. A decoder that reads one record, converts it, and moves on needs only enough memory for one record at a time, whatever the total size. This approach also lets the receiver start passing data on before the whole message has arrived, which reduces delay.

Batching what you pass on

Passing data to the consumer one item at a time is wasteful, because every handoff has fixed overhead. Group related items into reasonably sized batches before passing them on, for example all the records from one incoming message. Do not wait too long to build a batch, though, since delaying data hurts freshness. Often the best choice is to pass on whatever arrived together, and leave extra batching to a dedicated processor later in the pipeline.

Tip 1

One Message, One Batch

Convert everything in an incoming message into one group and pass it on together.

Tip 2

Do Not Hold Data Back

Avoid long waits to build bigger batches inside the receiver. Let a batch processor handle that.

Tip 3

Stream Large Inputs

Decode large inputs record by record to keep memory use flat.

Tip 4

Acknowledge After Handoff

Confirm receipt to the sender only once data has been safely passed to the consumer.

Partial acceptance

Sometimes part of a message is valid and part is not. A thoughtful receiver passes on the valid part and reports that some items were rejected, with a count, instead of refusing everything. Some protocols include a way of telling the sender exactly how many items were rejected, so the sender can decide what to do. Use such features where they exist, since they make failures understandable.

flowchart TD
  A[Message arrives] --> B[Decode records]
  B --> C{Record valid?}
  C -->|Yes| D[Convert and add to batch]
  C -->|No| E[Count and log rejection]
  D --> F[Pass batch to consumer]
  E --> F
  F --> G[Report accepted and rejected counts to sender]
        
FIG 6 — Handling a message that contains both valid and invalid records

12Error Handling and Passing Data On

What a receiver does when something goes wrong matters as much as what it does when all goes well.

What the consumer can tell you

When a receiver passes data to the next consumer, the answer can be success or an error. The Collector distinguishes errors that are permanent, meaning that retrying will never help, from errors that are temporary, meaning that retrying later may work. A permanent error might be data the pipeline rejects as invalid. A temporary error might be a downstream component that is briefly busy. Your receiver should pass this distinction on: tell senders to retry for temporary problems, and report permanent ones as failures that should not be retried.

Errors from the source

Errors that come from the source need equally careful handling. Distinguish a source that is unreachable from one that returned something unreadable, and from one that refused your credentials. Each calls for a different response. An unreachable source suggests waiting and trying again at the next interval. An unreadable answer suggests a bug or a version change that needs attention. A refused login suggests a configuration problem that retrying will not fix.

Error 1

Source Unreachable

Log at a suitable level, record the failure in your own metrics, and try again at the next interval without crashing.

Error 2

Unreadable Data

Skip the bad item, count it, and log enough detail to diagnose it without dumping sensitive content.

Error 3

Login Refused

Report a clear configuration error. Repeating the attempt quickly can lock the account.

Error 4

Downstream Busy

Respect the signal. Retry for temporary problems, give up on permanent ones, and tell senders honestly.

i
Log With Judgement

An error that repeats every ten seconds can flood the logs. Log the first occurrence clearly, then reduce repetition, and rely on metrics for counting. A quiet log with accurate counters is more useful than a loud one nobody reads.

Retrying with care

When a pull receiver fails to reach its source, it can simply wait for the next interval. But when a receiver must keep a connection alive, for example a stream of events, it needs a retry policy. A good policy waits a short time after the first failure, then longer after each further failure, up to a maximum, and adds a little random variation so that many receivers do not retry at exactly the same moment. Without this, a source that has just recovered may be knocked over again by a crowd of simultaneous reconnections.

1

Fail and note it

Record the failure in measurements and write one clear log line.

2

Wait a short time

Pause briefly before the first retry, so brief glitches pass without drama.

3

Wait longer each time

Increase the pause after each failure up to a sensible maximum, with a little random variation.

4

Reset on success

Once a connection works, return to the short pause so the next problem is handled quickly again.

Protecting the source and the Collector

A receiver should never retry in a tight loop. Rapid retries can overload the source, fill the logs, and consume processor time in the Collector. They can also cause account lockouts when the problem is a refused login. Always put a floor under the time between attempts, and give users a setting to adjust it.

i
Make Failure Boring

The best failure handling is almost invisible: a clear message at the start, steady counters, quiet retries and a smooth return to normal. Drama in the logs usually means the design has gaps.

13Making the Receiver Observable Itself

A receiver that cannot report on itself is a black box in the middle of your monitoring system.

The Collector offers helper tools that record standard figures about each receiver: how much data was accepted and how much was refused, for each signal. Using these helpers gives your receiver the same internal measurements as every other receiver, and these measurements appear in the Collector’s own metrics. Operators already know these figures, so they can tell at a glance whether your receiver is healthy.

MeasurementWhat it tells an operator
Data acceptedHow much the receiver successfully passed into the pipeline
Data refusedHow much the pipeline or the receiver turned away, hinting at overload or bad data
Collection failuresHow often scrapes failed, and for which reason
Duration of collectionsWhether the source is slowing down
Connection stateWhether the receiver can currently reach its source

Add measurements that are specific to your source

Beyond the standard figures, consider a few that reflect the source: the number of items parsed, the number of items skipped, the age of the newest reading, and the version of the source detected. These help diagnose the problems that are unique to your receiver. Keep them few and bounded, for the same reason you keep attributes bounded in the data you produce.

Logging that helps

Use the logger provided by the Collector rather than printing directly, so that log levels, formats and destinations follow the user’s configuration. Include context such as the receiver’s name and the source’s address, but never secrets such as passwords or tokens. At debug level, add more detail for engineers who are diagnosing a problem.

!
Silent Failure

The most dangerous receiver is one that fails quietly and keeps reporting nothing. A dashboard that shows flat zero looks the same as a healthy quiet system. Always provide a signal that distinguishes “no data because nothing happened” from “no data because collection is broken”.

Health and readiness

The Collector can expose a health signal that platforms use to decide whether to send traffic or restart a process. A receiver that cannot reach its source for a long time should make this visible, but should be careful not to cause pointless restarts. If the source is down, restarting the Collector will not bring it back. Reporting the problem through measurements and logs, while the Collector continues to serve other pipelines, is usually the right response.

Dashboards for your receiver

Provide an example dashboard or at least a list of recommended charts. Typical panels show data accepted per second, data refused per second, collection duration, collection failures by reason, and the age of the newest reading. Include a panel that compares the receiver’s view with the source’s own view where possible, such as the number of items the source says it has against the number the receiver passed on. A gap between them reveals lost data.

Panel 1

Throughput

Items accepted and refused per second, per signal.

Panel 2

Failures

Collection failures by reason, such as unreachable, unreadable and refused.

Panel 3

Freshness

Age of the newest reading, so stale data is obvious.

Panel 4

Source Cross-check

The source’s own counts beside the receiver’s, to expose silent loss.

Alerts Worth Having

Alert when collection has failed for several intervals in a row, when the age of the newest reading exceeds a threshold, and when refused data stays above zero for a sustained period. These three alerts catch the great majority of receiver problems.

14Testing Strategy

A receiver runs unattended for months, so testing must be thorough.

Level 1

Translation Tests

Feed sample source data into the conversion code and compare the output with expected telemetry. These tests are fast and need no network.

Level 2

Configuration Tests

Check that defaults are right, that valid files load, and that invalid ones are rejected with clear messages.

Level 3

Lifecycle Tests

Create, start and shut down the receiver using fake hosts and a recording consumer, checking that it stops cleanly and leaves nothing behind.

Level 4

Integration Tests

Run against a real or realistic instance of the source, often in a container, to verify the whole path from source to consumer.

Golden files

A popular technique is the golden file. You save the expected output of a conversion in a file. Future runs compare their output with the saved file, and any difference fails the test. This makes accidental changes to names, units or attributes visible at once. When a change is intended, you review the difference and update the file deliberately.

Hostile and unusual input

Test what happens with empty input, enormous input, truncated input, wrong types, missing fields and unexpected characters. Test slow sources, sources that disconnect midway, and sources that return old data. Receivers meet all of these in production sooner or later.

1

Collect real samples

Save genuine responses and messages from the source, with sensitive details removed, to use as test material.

2

Write translation tests first

They are quick and give confidence in the core of the receiver.

3

Add lifecycle and failure tests

Check clean shutdown, slow sources and broken connections.

4

Run an integration test in a container

Confirm that the receiver works against the real thing, including different versions of the source.

5

Measure performance

Benchmark conversion and check memory use under realistic and extreme load.

Testing across versions and environments

Sources change, so a receiver tested against one version may fail against another. Where practical, run integration tests against several versions of the source, at least the oldest and newest you claim to support. Containers make this affordable, since each version can be started in seconds and discarded afterwards. Keep the list of tested versions in the documentation, and update it as part of each release.

Also test on the platforms your users run. A receiver that works on one operating system may behave differently on another, especially around file paths, line endings, time handling and network defaults. If your users run on several platforms, run your tests on all of them in your automated build.

Testing the unhappy paths on purpose

Many receivers are tested only when everything goes right. Add tests where the source returns an error, where it returns nothing, where it returns half a message, where it closes the connection, and where it responds extremely slowly. Check that the receiver reports each situation correctly and recovers once the source is healthy again. These tests are the cheapest insurance against production surprises.

Unhappy pathWhat the test should confirm
Source returns an error statusThe failure is counted and logged, and the next round still runs
Source returns empty dataNo data is invented, and the receiver reports an empty round
Source returns truncated dataValid parts are kept and invalid parts are counted
Connection drops midwayThe receiver reconnects with backoff and does not duplicate data unexpectedly
Source is extremely slowThe time limit applies and the receiver does not block later rounds
Consumer refuses dataThe refusal is passed back or counted, and memory does not grow

15Security, Privacy and Governance

A receiver has access to credentials, networks and potentially sensitive data, so it carries real responsibility.

Handling credentials

The receiver may need a password, a key or a token to reach its source. Never write these into logs or error messages. Allow users to supply them through files or environment variables, and use the Collector’s types for sensitive text, which hide the value when settings are printed. Request the least access the receiver needs. A read-only account is far safer than an administrator account.

Encrypted connections

Where the source supports encryption, support it too, and make it easy to switch on. Use the standard encryption settings from the Collector so that users configure certificates the same way as with other components. Do not disable verification of certificates for convenience, even in examples, because examples are copied into real deployments.

Sensitive data in telemetry

Source data may contain personal details, secrets or internal addresses. Decide in advance which fields the receiver should pass on, which it should drop, and which it should leave for processors to handle. Offering a setting to exclude certain fields gives users control. Document clearly what the receiver collects, so that privacy and security reviews can proceed without reading the code.

!
Defaults Are Policy

Whatever your receiver collects by default is what most users will collect. Default to the minimum useful set, and make broader or more sensitive collection an explicit choice.

Supply Chain Care

Your receiver depends on libraries, and users will run your code inside a central piece of their infrastructure. Keep dependencies few, update them regularly, scan them for known problems, and publish releases in a way that lets users verify what they downloaded.

16Packaging, Distribution and Stability

Writing the receiver is only half the job. People must be able to use it, upgrade it and trust it.

Delivering it in a Collector

Most teams use the builder to produce their own Collector distribution that contains the receiver together with the other components they need. The result is a single program, which can be packaged as a container image or a standard installation package, and deployed in the same way as any other Collector. Keep the manifest in version control, so that anyone can rebuild the same distribution later.

Versioning and stability

The Collector uses stability levels to tell users how much to rely on a component: development, alpha, beta and stable. Declare an honest level for each signal your receiver supports. A new receiver should usually begin at a low level and rise as it gains users, tests and a settled configuration. Version your own module with a clear scheme, and describe changes in release notes, especially anything that alters names, units or settings.

Stability levelWhat users should expect
DevelopmentAnything may change, and the component may disappear
AlphaUsable for trials, with settings and output likely to change
BetaMostly settled, with changes announced and uncommon
StableChanges that break users are avoided and follow a long notice period

Contributing upstream

If your source is popular, consider offering the receiver to the community’s contributed components. The project has guidelines on structure, documentation, tests and ownership. Contributing brings reviews, shared maintenance and users, but also obligations: someone must answer questions and keep the receiver working as the Collector evolves. If the source is private, keep the receiver in your own repository, and still follow the same conventions, so that it can be shared later without rewriting.

i
Document Early

Write the documentation as you build: what the receiver collects, every setting with its default, example configurations and known limits. Documentation written afterwards is usually thinner and often wrong.

Release routine and upgrades

A dependable release routine builds trust. Each release should be built automatically from tagged source, tested on all supported platforms, and published with notes that explain what changed. Users read these notes to decide when to upgrade, so highlight anything that alters settings, names, units or behaviour. Where a change would break existing users, provide a transition period in which both the old and new forms work and the old form is reported as deprecated.

Keep pace with the Collector itself. New releases of the Collector libraries bring new features, fixes and sometimes changes to programming interfaces. Plan regular upgrades, rather than letting your receiver fall many versions behind, because the longer you wait the harder the jump becomes. Automated checks that build and test against the latest Collector libraries reveal breakage early.

1

Tag a release

Mark a specific version of the source so that anyone can rebuild exactly the same code.

2

Build and test automatically

Run all tests on every supported platform before publishing anything.

3

Publish with clear notes

Describe changes, especially those that affect settings, names, units or behaviour.

4

Track upstream Collector releases

Upgrade the libraries on a regular schedule and fix breakage while it is small.

!
Do Not Break Names Quietly

Renaming a metric, changing its unit or altering an attribute silently breaks every dashboard and alert built on it. Announce such changes, support both forms for a period, and give users time to adapt.

17Performance and Resource Use

A receiver shares a process with every other component, so its appetite affects everyone.

Memory

Memory is the most common problem. Avoid loading entire large inputs at once when they can be processed piece by piece. Release references to data once it has been passed on. Be careful with caches and maps that grow with the number of distinct items seen, because they can grow without limit over weeks of running. Bound every cache, and decide what happens when the bound is reached.

Processor time

Conversion code runs for every item, so even small inefficiencies add up. Avoid repeating work that can be done once, such as building lookup tables. Avoid unnecessary copying of text. Measure before optimising, using benchmarks and profiling tools, because guesses about speed are often wrong.

Concurrency

Push receivers handle many arrivals at once, and the code that converts and forwards them must be safe when run in parallel. Shared data needs protection, and per-request data should stay private to each request. Test with many simultaneous senders, and use the language’s race-detection tools to catch mistakes that ordinary tests miss.

Bound
Every cache, queue and buffer
Measure
Benchmark before tuning
Race-test
Check concurrent code explicitly
i
Work With the Memory Limiter

Collectors usually include a memory limiter that refuses data when memory is high. Make sure your receiver reacts properly when its consumer refuses data, so the protection works as intended rather than being undermined by your own buffering.

Finding problems with measurement

Go provides profiling tools that show where time and memory are spent. Run your receiver under a realistic load, collect a profile, and read it. You will often find surprises: a conversion step that copies text repeatedly, a map that grows without limit, or a lock that many threads fight over. Fixing the biggest item on the list usually gives most of the improvement, and later rounds give smaller gains.

Test with the largest realistic input and with a sustained load over many hours. Many memory problems appear only after a long run, as small leaks accumulate. Watch the receiver’s memory use over a day under steady load. A line that climbs slowly and never falls is a leak, even if each hour looks harmless.

Tool 1

CPU Profile

Shows which functions use the most processor time during conversion and handling.

Tool 2

Memory Profile

Shows what holds memory and where allocations come from.

Tool 3

Long Soak Test

Runs for many hours under steady load to expose slow leaks.

Tool 4

Race Detector

Reports unsafe sharing of data between concurrent tasks.

i
Set a Budget

Decide how much memory and processor time the receiver may use at a given load, write it down, and check it in automated tests. A written budget turns vague worries about performance into a clear pass or fail.

18Design Patterns and Anti-patterns

Some choices work again and again, and some mistakes repeat just as often.

Helpful patterns

Pattern: Separate Transport from Translation

Keep the code that obtains data apart from the code that converts it. Conversion can then be tested with plain sample files and reused for other transports.

Pattern: Reuse Standard Settings and Helpers

Use the Collector’s ready-made groups for servers, clients, schedules and authentication, and its helper libraries for scrapers and for internal measurements. Your receiver will behave like every other and need less testing.

Pattern: Metadata-driven Definitions

Describe metrics and attributes in a metadata file and generate code and documentation from it. This keeps names, units and documents consistent.

Pattern: Fail Visibly and Recover Quietly

Report failures through measurements and logs, then carry on at the next interval. The receiver should never crash the Collector because of a problem in its source.

Harmful anti-patterns

ANTI-PATTERN-01 Avoid
Problem

Starting background work in the start action without a way to stop it.

Why It’s Harmful

Shutdown leaves threads and connections behind, which causes leaks, stuck restarts and data loss during upgrades.

Correct Approach

Create a cancellable context for background work, and have shutdown cancel it and wait for the work to finish.

ANTI-PATTERN-02 Avoid
Problem

Turning unbounded values, such as identifiers or full request paths, into metric attributes.

Why It’s Harmful

Series multiply without limit, so storage costs rise and queries slow down.

Correct Approach

Use small, bounded sets of attribute values. Put detailed values in logs or traces instead.

ANTI-PATTERN-03 Avoid
Problem

Swallowing errors and returning empty results when the source fails.

Why It’s Harmful

Dashboards show a flat line that looks like a healthy quiet system, so real outages go unnoticed.

Correct Approach

Record failures in measurements, log them with suitable detail, and make a broken collection distinguishable from silence.

ANTI-PATTERN-04 Avoid
Problem

Inventing private names and units for data that standard conventions already describe.

Why It’s Harmful

Dashboards and alerts written for standard names do not work, and every user must write extra mapping.

Correct Approach

Adopt the published semantic conventions, and add custom names only where nothing standard exists.

19Best Practices and Common Mistakes

A checklist in plain language, drawn from everything above.

1

Check for an existing receiver first

Configuring or extending something that exists is cheaper than owning something new.

2

Plan the data model before the code

Decide what to collect, how to map it and what to name it, using the standard conventions.

3

Use the helpers the Collector provides

Settings groups, scraper helpers, test tools and the metadata generator all reduce effort and error.

4

Keep start quick and shutdown complete

Start returns promptly, and shutdown releases everything the receiver holds.

5

Treat input as hostile

Validate, limit and time-box everything that arrives from outside.

6

Make the receiver observable

Record accepted, refused and failed counts, and make broken collection visible.

7

Test at several levels and document as you go

Translation, configuration, lifecycle and integration tests, plus a written guide, make the receiver safe to adopt.

!
Frequent Mistakes

The usual errors are skipping the planning, ignoring shutdown, producing high-cardinality attributes, swallowing source errors, trusting incoming data, and forgetting that the Collector libraries change between versions. Every one has caused real outages, usually discovered long after the receiver was first deployed.

A sensible order of work

Build the translation code first and test it with samples. Then add the simplest possible transport, and verify with a recording consumer. Then add configuration and validation. Then add measurements and logging. Then harden against bad input and slow sources. Finally, package, document and release. Following this order gives you something working early and lets each layer be tested before the next is added.

20Real-World Scenarios

A few typical stories show how the ideas fit together.

Reading a Legacy Application’s Status Page

A company runs an old application that publishes a plain status page of counters. The team builds a pull receiver that fetches the page every thirty seconds, converts each counter into a cumulative sum or a gauge, and sets the application’s name and version as resource attributes. Because the page is simple, most of the effort goes into choosing correct metric types and testing with saved samples.

Receiving Events from a Proprietary Device

A manufacturer’s devices send binary messages over a network connection. The team builds a push receiver that listens on a port, decodes each message defensively, and turns it into log records with the device identifier on the resource. Size limits, timeouts and authentication protect the Collector from faulty devices, and refusals are signalled so devices can retry later.

Exposing Business Numbers

A retail team wants order counts and queue lengths from a business system beside technical metrics. A pull receiver queries a reporting interface once a minute, using a read-only account, and converts the answers into gauges. Bounded attributes such as region and channel keep cardinality low.

Bridging a Message Queue

Events from an internal queue are consumed by a receiver that converts them into logs and trace spans. The team keeps conversion in a separate package, so when the queue technology changes, only the transport part is rewritten.

What the scenarios share

In each case, the team studied the source before coding, chose the family that suited it, separated translation from transport, built in protection and measurements, and tested with real samples. Where things went wrong in other teams, one of these steps had been skipped.

A Team That Skipped Planning

A team built a receiver in a week, mapping every source counter to a gauge because it was quickest. Months later, users complained that rate charts were wrong and resets looked like crashes. Fixing the types meant changing metric definitions that dashboards already used, which caused a painful migration. An hour of planning at the start would have prevented it.

A Team That Forgot Shutdown

A receiver opened a background loop but never stopped it. During rolling upgrades, old Collector processes lingered and kept polling the source, doubling the load on a fragile system. Adding a proper shutdown that cancelled the loop fixed the problem, and a lifecycle test now guards against its return.

A Team That Made Failure Visible

When a source’s login expired, the receiver reported a clear configuration error, raised a counter, and slowed its retries. The on-call engineer saw the alert, renewed the credential and data resumed within minutes. Because the failure was visible rather than silent, nobody lost days of data without noticing.

21Frequently Asked Questions

Short, plain answers to the questions people ask most often.

Q1Which programming language do I need?

Receivers for the Collector are written in Go, because the Collector itself is a Go program. Sources may be in any language, since the receiver only talks to them through a protocol or interface.

Q2Can a receiver support more than one signal?

Yes. The factory declares which signals are supported, and a receiver can handle traces, metrics and logs, or any subset, with a separate stability level for each.

Q3Should I build a push or a pull receiver?

Let the source decide. If it can send data by itself, a push receiver is natural. If it only answers when asked, build a pull receiver using the scraper helpers.

Q4How do I add my receiver to a Collector?

Use the Collector builder with a manifest that lists your receiver’s module among the components. The builder produces a Collector distribution that includes it, and you configure it in the usual configuration file.

Q5What is the metadata generator for?

It reads a description of your metrics and attributes and generates the repetitive code, documentation and tests, so the receiver stays consistent and you write less boilerplate.

Q6Why must the start action return quickly?

The Collector waits for each component to start before moving on. A start action that blocks would stall the whole Collector. Launch long-running work in the background and return.

Q7What should I do when the source is down?

Do not crash. Record the failure in measurements, log it sensibly, and try again at the next interval. Make sure operators can tell the difference between a quiet source and a broken collection.

Q8How do I avoid high cardinality?

Keep attribute values to a small, bounded set. Move values that vary without limit, such as identifiers, into logs or traces, or drop them.

Q9How do I test without a real source?

Use saved sample data for translation tests, fake hosts and recording consumers for lifecycle tests, and containers running a real or simulated source for integration tests.

Q10Do I need to contribute the receiver to the community?

No. Private receivers can live in your own repository. Contributing makes sense for popular sources, and brings shared maintenance along with the obligation to keep it working.

Q11How stable are the Collector programming interfaces?

They evolve between versions, and names of types and functions do change. Pin the library versions you use, read release notes when upgrading, and confirm details in the documentation for your version.

Q12Should my receiver compute rates or averages?

Usually not. Report raw totals or readings with correct types and let the backend compute rates. Calculations inside the receiver need remembered state and break on restart.

Q13Where should secrets such as passwords be stored?

Not in the configuration text itself where possible. Allow files or environment variables, use the Collector’s types for sensitive values, and never write secrets to logs.

Q14How do I know my receiver is working in production?

Watch the accepted and refused counts, collection failure counts and collection durations, and provide a clear indicator that distinguishes a quiet source from a failed collection.

22Summary and Key Takeaways

A custom receiver is a translator between a source the Collector does not yet understand and the common language of OpenTelemetry. To build one well, you study the source, decide how its data maps onto metrics, logs or traces, and choose between listening and fetching. You then assemble the standard parts: a configuration with sensible defaults and clear validation, a factory, a receiver with an honest start and shutdown, translation code that produces a well-formed data model, and a way of passing results to the next consumer.

The difference between a receiver that merely works and one that can be trusted lies in the details: bounded attributes, defensive handling of input, honest error signalling, measurements about itself, thorough tests and clear documentation. Use the tools the Collector gives you, follow its conventions, and keep translation separate from transport, and your receiver will be easier to build, safer to run and simpler to hand on to others.

“A good receiver is a polite guest in someone else’s pipeline: quick to start, careful with what it brings in, honest when something is wrong, and tidy when it leaves.”

Key Takeaways

  • Plan before coding — study the source, decide what to collect and map each item to the right kind of telemetry using standard names.
  • Choose push or pull deliberately — listeners suit sources that send data, scrapers suit sources that answer when asked.
  • Assemble the standard parts — configuration, defaults, validation, factory, receiver instance and next consumer, with the Collector’s helpers wherever possible.
  • Respect the lifecycle — start quickly, run in the background with its own context, and shut down completely.
  • Model data carefully — identity on the resource, bounded attributes on points, correct units, timestamps, start times and metric types.
  • Be honest about errors — separate permanent from temporary failures, signal backpressure, and never hide a broken collection behind a flat line.
  • Test, secure and document — translation, lifecycle and integration tests, safe handling of credentials and input, and written guidance, released with an honest stability level.