Set up Cribl to send data to HLA without a MID server<!-- /*NS Branding Styles*/ --> .ns-kb-css-body-editor-container { p { font-size: 12pt; font-family: Lato; color: var(--now-color--text-primary, #000000); } span { font-size: 12pt; font-family: Lato; color: var(--now-color--text-primary, #000000); } h2 { font-size: 24pt; font-family: Lato; color: var(--now-color--text-primary, black); } h3 { font-size: 18pt; font-family: Lato; color: var(--now-color--text-primary, black); } h4 { font-size: 14pt; font-family: Lato; color: var(--now-color--text-primary, black); } a { font-size: 12pt; font-family: Lato; color: var(--now-color--link-primary, #00718F); } a:hover { font-size: 12pt; color: var(--now-color--link-primary, #024F69); } a:target { font-size: 12pt; color: var(--now-color--link-primary, #032D42); } a:visited { font-size: 12pt; color: var(--now-color--link-primary, #00718f); } ul { font-size: 12pt; font-family: Lato; } li { font-size: 12pt; font-family: Lato; } img { display: ; max-width: ; width: ; height: ; } } This article walks through connecting a Cribl Stream instance to the ServiceNow ITOM ingest gateway so that log data flows from Cribl into ServiceNow Health Log Analytics without a MID server. It covers two source types: OpenTelemetry (OTel) and syslog. If you have other source types, you can use the syslog example as a guide to transform other log shapes into OTel logs. Using the command line is recommended, but instructions are also provided for using the Cribl UI. Table of Contents PrerequisitesStep 1 — Create a ServiceNow Data Input and Cribl Destination Create the Data Input (ServiceNow UI)Authenticate with Cribl StreamCreate the Cribl OTel gRPC Destination Using the Cribl UI Step 2 — Connect an Existing OTel Source to the Destination List Existing OTel SourcesCreate a Passthrough PipelineCreate the RouteVerify Data is Flowing Step 3 — Connect a Syslog Source via a Reformatting Pipeline Create the Syslog Eval PipelineCreate the Syslog RouteVerify Data is Flowing Prerequisites Before you begin, have the following ready: A ServiceNow instance with HLA installed and enabled and login credentials providing the evt_mgmt_admin roleCribl Stream (cloud or self-hosted) and login credentials with an OpenTelemetry (OTel) or syslog destination receiving log data; If you don't have either set up, refer to the Cribl documentation: Cribl OpenTelemetry SourceCribl Syslog Source Network permissions allow for for sending data to an API endpoint hosted on service-now.com That's all you need to set everything up via the UI. To use the command line examples, you'll need curl and jq installed on your workstation and the following environment variables configured: export SN_INSTANCE="https://..." export SN_USERNAME="..." export SN_PASSWORD="..." export CRIBL_BASE_URL="https://..." export CRIBL_USERNAME="..." export CRIBL_PASSWORD="..." Step 1 — Create a ServiceNow Data Input and Cribl Destination This step produces the thre values that Cribl uses to send logs to the ServiceNow gateway: ValueWhat it isIntegration IDIdentifies this integration to ServiceNow (servicenow-integration-id)Access tokenJWT credential passed with every request (servicenow-access-token)Gateway gRPC endpointThe address to which Cribl Stream will send data Create the Data Input (ServiceNow UI) The Integration Launchpad wizard is the easiest way to create a Data Input and obtain all three values in one step. In your ServiceNow instance, navigate to Integration LaunchpadCreate a new Cribl Stream (MID-less) data inputComplete and save the wizard After saving, the UI presents the Integration ID, Endpoint URL, and Access token. Save these values. If you're using the command line, set them as environment variables before continuing; subsequent commands reference them directly: export SN_INTEGRATION_ID="<Integration ID from wizard>" export SN_ACCESS_TOKEN="<Access token from wizard>"export SN_GRPC_ENDPOINT="<host:port Endpoint URL from wizard>" Verify: Confirm the Data Input record was created using the ServiceNow Table API: curl -s -u "$SN_USERNAME:$SN_PASSWORD" \ "$SN_INSTANCE/api/now/table/sn_occ_base_data_input_config/$SN_INTEGRATION_ID\ ?sysparm_display_value=all" \ | jq '{sys_id: .result.sys_id.value, name: .result.name.value, status: .result.status.value}' A status value of 1 indicates the Data Input is active. Authenticate with Cribl Stream Create a Cribl API token. Refer to the Cribl documentation, as the process differs between local and cloud Cribl instances. Note: If you plan on using the Cribl UI exclusively, you can skip this step. It is only necessary when using the command line and Cribl API. For a local instance, you can authenticate and produce a token using the API using the command below. CRIBL_TOKEN=$(curl -sf -X POST "$CRIBL_BASE_URL/api/v1/auth/login" \ -H "Content-Type: application/json" \ -d "{\"username\":\"$CRIBL_USERNAME\",\"password\":\"$CRIBL_PASSWORD\"}" \ | jq -r '.token') Regardless of authentication method, be sure to capture the token in the CRIBL_TOKEN environment variable. Create the Cribl OTel gRPC Destination Now we'll use the token to create a Cribl Destination that points to the Data Input we configured on our ServiceNow instance. The integration ID and access token are configured as metadata values, allowing this Destination to authenticate against the ServiceNow API. curl -vsf -X POST "$CRIBL_BASE_URL/api/v1/system/outputs" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg id "sn_otel_${SN_INTEGRATION_ID}" \ --arg endpoint "${SN_GRPC_ENDPOINT}" \ --arg intid "${SN_INTEGRATION_ID}" \ --arg token "${SN_ACCESS_TOKEN}" \ '{ id: $id, type: "open_telemetry", protocol: "grpc", otlpVersion: "1.3.1", endpoint: $endpoint, compress: "gzip", authType: "none", concurrency: 5, timeoutSec: 30, metadata: [ {key: "servicenow-integration-id", value: ("\"" + $intid + "\"")}, {key: "servicenow-access-token", value: ("\"" + $token + "\"")} ], description: "ServiceNow ITOM ingest" }')" Important: metadata values are JavaScript expressions, not plain strings. The jq command above wraps each value in escaped quotes so Cribl evaluates them as string literals at runtime. See the Cribl OpenTelemetry destination docs for full field reference. Verify: Confirm the destination was created: curl -sf "$CRIBL_BASE_URL/api/v1/system/outputs/sn_otel_${SN_INTEGRATION_ID}" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ | jq '{id: .items[0].id, type: .items[0].type, endpoint: .items[0].endpoint}' Using the Cribl UI Go to Data > Destinations, click Add Destination, and choose OpenTelemetry. Set the Protocol to gRPC and enter the gateway endpoint (host:port only, no scheme). Under gRPC Metadata, add two entries: servicenow-integration-id with the Integration ID as the value, and servicenow-access-token with the access token. Values produced in this step — keep these for Steps 2 and 3: ValueVariable / ValueIntegration ID$SN_INTEGRATION_IDAccess token (JWT)$SN_ACCESS_TOKENGateway gRPC endpoint$SN_GRPC_ENDPOINTCribl destinationsn_otel_${SN_INTEGRATION_ID} Step 2 — Connect an Existing OTel Source to the Destination OTel sources send data in OTLP format, which ServiceNow accepts natively — no field transformation is needed. This step creates a passthrough pipeline and a route that wires the OTel source to the destination created in Step 1. List Existing OTel Sources curl -sf "$CRIBL_BASE_URL/api/v1/system/inputs" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ | jq '.items[] | select(.type == "open_telemetry") | {id, port, disabled}' Save the source ID you want to connect, setting an environment variable if you're using the command line: export SOURCE_ID="<id of the source from the list above>" Create a Passthrough Pipeline Because data is already in OTLP format, the pipeline needs no functions: curl -sf -X POST "$CRIBL_BASE_URL/api/v1/pipelines" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"id\": \"sn_otel_${SN_INTEGRATION_ID}\", \"description\": \"OTel passthrough to ServiceNow ITOM\", \"conf\": {\"functions\": []} }" Verify: Confirm the pipeline was created with no functions: curl -sf "$CRIBL_BASE_URL/api/v1/pipelines/sn_otel_${SN_INTEGRATION_ID}" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ | jq '{id: .items[0].id, functions: (.items[0].conf.functions | length)}' Using the Cribl UI Go to Processing > Pipelines and click Add Pipeline. Set the ID to sn_otel_<your Integration ID> and optionally add a description. Do not add any functions. Leave the pipeline empty. Click Save. Create the Route Cribl routes are stored as an ordered list. The calls below fetch the current list, prepend the new route, and write it back: ROUTE_TABLE=$(curl -sf "$CRIBL_BASE_URL/api/v1/routes" \ -H "Authorization: Bearer $CRIBL_TOKEN") NEW_ROUTE=$(jq -n \ --arg id "sn_route_${SN_INTEGRATION_ID}" \ --arg filter "__inputId=='open_telemetry:${SOURCE_ID}'" \ --arg pipeline "sn_otel_${SN_INTEGRATION_ID}" \ --arg output "sn_otel_${SN_INTEGRATION_ID}" \ '{id: $id, name: $id, filter: $filter, pipeline: $pipeline, output: $output, final: true, description: "OTel → ServiceNow ITOM"}') UPDATED=$(echo "$ROUTE_TABLE" \ | jq --argjson r "$NEW_ROUTE" '.items[0].routes = [$r] + .items[0].routes | .items[0]') curl -sf -X PATCH "$CRIBL_BASE_URL/api/v1/routes/default" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ -H "Content-Type: application/json" \ -d "$UPDATED" The filter __inputId=='open_telemetry:${SOURCE_ID}' matches only events from the chosen source. Setting "final": true prevents matched events from falling through to subsequent routes. See the Cribl routing docs for details. Verify: Confirm the route was created: curl -sf "$CRIBL_BASE_URL/api/v1/routes" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ | jq '.items[0].routes[] | select(.id == "sn_route_\(env.SN_INTEGRATION_ID)") | {id, filter, pipeline, output, final}' Using the Cribl UI Go to Routing > Data Routes and add a route: set the filter to your OTel source, select the passthrough pipeline, and choose sn_otel_${SN_INTEGRATION_ID} as the output. Enable Final. Verify Data is Flowing The HLA mapper table can be queried to verify data is being flowing end-to-end from Cribl to ServiceNow. curl -s -u "$SN_USERNAME:$SN_PASSWORD" \ "$SN_INSTANCE/api/now/table/sn_occ_mapper_example\ ?sysparm_query=data_input=$SN_INTEGRATION_ID^ORDERBYDESCsys_created_on\ &sysparm_limit=5" \ | jq '{count: (.result | length), latest: .result[0].sys_created_on}' Alternatively, check the Data Input in the ServiceNow Integration Launchpad UI for ingestion metrics. Step 3 — Connect a Syslog Source via a Reformatting Pipeline If you only need to send OTel data to ServiceNow, you can stop after Step 2. This step is only required for syslog sources and serves as an example for ingesting sources that require a Cribl Eval Pipeline to build OTLP logs from their native format. Syslog data is not in OTLP format. Cribl must map parsed syslog fields to OTel semantic conventions before forwarding to ServiceNow. This is handled by a two-function pipeline: an Eval function that builds the OTLP log structure, followed by an OTLP Logs function that serializes and batches the events. Both the syslog pipeline and the OTel pipeline from Step 2 send data to the same destination (sn_otel_${SN_INTEGRATION_ID}) created in Step 1. Find the syslog source ID you want to connect (use the UI or make a request to /api/v1/system/inputs and filter for type "syslog" if you need to look it up). Set an environment variable if you're using the command line: export SOURCE_ID="<id of your syslog source>" Reference: Syslog to OTel field mapping The mapping between OTLP and Cribl Syslog fields in this table drives the setup of our Eval function. OTLP fieldCribl syslog fieldNotestime_unix_nano_timeCribl internal timestamp, multiplied by 10⁹ to get nanosecondsobserved_time_unix_nano_timeSame as aboveseverity_textseverityNamee.g. info, warning, debugseverity_numberseverityNamedebug→7, info→6, notice→5, warning→4, all others→3bodymessageThe raw syslog message textresource.attributes.hosthostOriginating hostnameresource.attributes.sourcesourceSyslog source identifierattributes.facilityfacilityNumeric syslog facility codeattributes.facilityNamefacilityNameHuman-readable facility (e.g. local0)attributes.appnameappnameSyslog APP-NAME fieldattributes.procidprocidSyslog PROCID field Create the Syslog Eval Pipeline This pipeline has two functions: An Eval function that builds the OTLP log structure from syslog fieldsAn OTLP Logs serialization function that batches and frames the events curl -sf -X POST "$CRIBL_BASE_URL/api/v1/pipelines" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"id\": \"sn_syslog_${SN_INTEGRATION_ID}\", \"description\": \"Syslog to OTel semantic conventions for ServiceNow ITOM\", \"conf\": { \"functions\": [ { \"id\": \"eval\", \"filter\": \"true\", \"disabled\": false, \"description\": \"Map syslog fields to OTLP log structure\", \"conf\": { \"add\": [ {\"name\": \"__otlp\", \"value\": \"({ version: '1.3.1', type: 'logs', extracted: true })\"}, {\"name\": \"time_unix_nano\", \"value\": \"_time * 1e9\"}, {\"name\": \"observed_time_unix_nano\", \"value\": \"_time * 1e9\"}, {\"name\": \"severity_text\", \"value\": \"severityName\"}, {\"name\": \"severity_number\", \"value\": \"severityName == 'debug' ? 7 : severityName == 'info' ? 6 : severityName == 'notice' ? 5 : severityName == 'warning' ? 4 : 3\"}, {\"name\": \"body\", \"value\": \"message\"}, {\"name\": \"resource\", \"value\": \"({ attributes: { host: host, source: source } })\"}, {\"name\": \"attributes\", \"value\": \"({ facility: facility, facilityName: facilityName, appname: appname, procid: procid })\"} ], \"remove\": [] } }, { \"id\": \"otlp_logs\", \"filter\": \"true\", \"disabled\": false, \"conf\": { \"dropNonLogEvents\": true, \"batchOTLPLogs\": true, \"sendBatchSize\": 8192, \"timeout\": 200, \"sendBatchMaxSize\": 0, \"metadataCardinalityLimit\": 1000 } } ] } }" Verify: Confirm the pipeline was created with both functions in order: curl -sf "$CRIBL_BASE_URL/api/v1/pipelines/sn_syslog_${SN_INTEGRATION_ID}" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ | jq '{id: .items[0].id, functions: [.items[0].conf.functions[].id]}' Using the Cribl UI Go to Data > Pipelines and click Add Pipeline. Set the ID to sn_otel_<your Integration ID> and optionally add a description. Click Save.With the new pipeline open, click Add Function and select Eval. In the Add Fields section each of the following name value pairs: NameValue__otlp({ version: '1.3.1', type: 'logs', extracted: true }) time_unix_nano _time * 1e9observed_time_unix_nano_time * 1e9severity_textseverityNameseverity_numberseverityName == 'debug' ? 7 : severityName == 'info' ? 6 : severityName == 'notice' ? 5 : severityName == 'warning' ? 4 : 3bodymessageresource.attributes.source({ attributes: { host: host, source: source } }) attributes({ facility: facility, facilityName: facilityName, appname: appname, procid: procid }) Click Save on the Eval function. Click Add Function again and select OTLP Logs. Enable Drop Non-Log Events and Batch OTLP Logs, set Send Batch Size to 8192 and leave Send Batch Max Size at 0. Click Save, then Save the pipeline. Create the Syslog Route Use the same fetch-prepend-PATCH pattern from Step 2: ROUTE_TABLE=$(curl -sf "$CRIBL_BASE_URL/api/v1/routes" \ -H "Authorization: Bearer $CRIBL_TOKEN") NEW_ROUTE=$(jq -n \ --arg id "sn_route_${SN_INTEGRATION_ID}" \ --arg filter "__inputId=='syslog:${SOURCE_ID}'" \ --arg pipeline "sn_syslog_${SN_INTEGRATION_ID}" \ --arg output "sn_otel_${SN_INTEGRATION_ID}" \ '{id: $id, name: $id, filter: $filter, pipeline: $pipeline, output: $output, final: true, description: "Syslog → ServiceNow ITOM"}') UPDATED=$(echo "$ROUTE_TABLE" \ | jq --argjson r "$NEW_ROUTE" '.items[0].routes = [$r] + .items[0].routes | .items[0]') curl -sf -X PATCH "$CRIBL_BASE_URL/api/v1/routes/default" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ -H "Content-Type: application/json" \ -d "$UPDATED" The output field points to sn_otel_${SN_INTEGRATION_ID} — the same destination used in Step 2. The pipeline converts the data to OTLP format before it leaves Cribl. Verify: Confirm the route was created: curl -sf "$CRIBL_BASE_URL/api/v1/routes" \ -H "Authorization: Bearer $CRIBL_TOKEN" \ | jq '.items[0].routes[] | select(.id == "sn_route_\(env.SN_INTEGRATION_ID)") | {id, filter, pipeline, output, final}' Using the Cribl UI Go to Routing > Data Routes and add a route: set the filter to your syslog source, select the eval pipeline, and choose sn_otel_${SN_INTEGRATION_ID} as the output. Enable Final. Verify Data is Flowing Similar to Step 2, verify data is flowing by querying the HLA mapper table or checking the Data Input in the ServiceNow IntegrationLaunchpad UI shows nonzero ingestion metrics.