Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ services:
splunk:
image: "splunk/splunk:${SPLUNK_VERSION}"
container_name: splunk
platform: linux/amd64
environment:
- SPLUNK_START_ARGS=--accept-license
- SPLUNK_GENERAL_TERMS=--accept-sgt-current-at-splunk-com
Expand Down
6 changes: 6 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from splunklib import client

service = client.connect(host="localhost", username="admin", password="changed!", autologin=True)

for app in service.apps:
print(app.setupInfo)
3 changes: 3 additions & 0 deletions splunklib/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
from time import sleep
from urllib import parse

from splunklib.internal.telemetry.sdk_usage import log_telemetry_sdk_usage

from . import data
from .data import record
from .binding import (
Expand Down Expand Up @@ -355,6 +357,7 @@ def connect(**kwargs):
"""
s = Service(**kwargs)
s.login()
log_telemetry_sdk_usage(s, module="custom_script")
return s


Expand Down
13 changes: 13 additions & 0 deletions splunklib/internal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright © 2011-2025 Splunk, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"): you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
17 changes: 17 additions & 0 deletions splunklib/internal/telemetry/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright © 2011-2025 Splunk, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"): you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from .sender import TelemetrySender
from .metric import Metric, MetricType
from .sdk_usage import log_telemetry_sdk_usage
30 changes: 30 additions & 0 deletions splunklib/internal/telemetry/metric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright © 2011-2025 Splunk, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"): you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from dataclasses import dataclass
from enum import Enum
from typing import Dict


class MetricType(Enum):
Event = "event"
Aggregate = "aggregate"


@dataclass
class Metric:
type: MetricType
component: str
data: Dict
opt_in_required: int # TODO: find what the values mean
35 changes: 35 additions & 0 deletions splunklib/internal/telemetry/sdk_usage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import sys
import splunklib
from .metric import Metric, MetricType
from .sender import TelemetrySender

import logging
from splunklib import setup_logging

setup_logging(logging.DEBUG)

log = logging.getLogger()

SDK_USAGE_COMPONENT = "splunk.sdk.usage"


# FIXME: adding Service typehint produces circular dependency
def log_telemetry_sdk_usage(service, **kwargs):
metric = Metric(
MetricType.Event,
SDK_USAGE_COMPONENT,
{
"sdk-language": "python",
"python-version": sys.version,
"sdk-version": splunklib.__version__,
**kwargs,
},
4,
)
try:
log.debug(f"sending new telemetry {metric}")
telemetry = TelemetrySender(service)
# TODO: handle possible errors
_, _ = telemetry.send(metric)
except Exception as e:
log.error("Could not send telemetry", exc_info=True)
70 changes: 70 additions & 0 deletions splunklib/internal/telemetry/sender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright © 2011-2025 Splunk, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"): you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from typing import Any, Optional, Tuple
from splunklib.data import Record
from splunklib.internal.telemetry.metric import Metric
import json

# TODO: decide: either struggle with the type hints or get rid of them and stick to the convention

CONTENT_TYPE = [('Content-Type', 'application/json')]
DEFAULT_TELEMETRY_USER = "nobody" # User `nobody` always exists
DEFAULT_TELEMETRY_APP = "splunk_instrumentation" # This app is shipped with Splunk and has `telemetry-metric` endpoint
TELEMETRY_ENDPOINT = "telemetry-metric"


class TelemetrySender:
# FIXME: adding Service typehint produces circular dependency
# service: Service

def __init__(self, service):
self.service = service

def send(self, metric: Metric, user: Optional[str] = None, app: Optional[str] = None) -> Tuple[Record, Any]:
"""Sends the metric to the `telemetry-metric` endpoint.

:param user: Optional user that sends the telemetry.
:param app: Optional app that is used to send the telemetry.

If those values are omitted, the default values are used.
This makes sure that, even if missing some info, the event will be sent.
"""

metric_body = self._metric_to_json(metric)

user = user or DEFAULT_TELEMETRY_USER
app = app or DEFAULT_TELEMETRY_APP

response = self.service.post(
"telemetry-metric",
user,
app,
headers=[('Content-Type', 'application/json')],
body=metric_body,
)

body = json.loads(response.body.read().decode('utf-8'))

return response, body

def _metric_to_json(self, metric: Metric) -> str:
m = {
"type": metric.type.value,
"component": metric.component,
"data": metric.data,
"optInRequired": metric.opt_in_required
}

return json.dumps(m)
3 changes: 3 additions & 0 deletions splunklib/modularinput/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from .input_definition import InputDefinition
from .validation_definition import ValidationDefinition

from splunklib.internal.telemetry import log_telemetry_sdk_usage


class Script(metaclass=ABCMeta):
"""An abstract base class for implementing modular inputs.
Expand Down Expand Up @@ -66,6 +68,7 @@ def run_script(self, args, event_writer, input_stream):
self._input_definition = InputDefinition.parse(input_stream)
self.stream_events(self._input_definition, event_writer)
event_writer.close()
log_telemetry_sdk_usage(self.service, module="modularinput")
return 0

if str(args[1]).lower() == "--scheme":
Expand Down
5 changes: 5 additions & 0 deletions splunklib/searchcommands/search_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
from warnings import warn
from xml.etree import ElementTree

from splunklib.internal.telemetry.sdk_usage import log_telemetry_sdk_usage

# Relative imports
from . import Boolean, Option, environment
from .internals import (
Expand Down Expand Up @@ -468,6 +470,9 @@ def process(
else:
self._process_protocol_v2(argv, ifile, ofile)

cmd_name = self.__class__.__name__
log_telemetry_sdk_usage(self.service, module=cmd_name)

def _map_input_header(self):
metadata = self._metadata
searchinfo = metadata.searchinfo
Expand Down
4 changes: 2 additions & 2 deletions tests/system/test_apps/modularinput_app/bin/modularinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
from splunklib.modularinput import Scheme, Argument, Script, Event


class ModularInput(Script):
endpoint_arg = "endpoint"

Expand All @@ -47,10 +46,11 @@ def validate_input(self, definition):
raise ValueError(f"non-supported scheme {parsed.scheme}")

def stream_events(self, inputs, ew):

for input_name, input_item in list(inputs.inputs.items()):
event = Event()
event.stanza = input_name
event.data = "example message"
event.data = f"New endpoint received: {input_item[self.endpoint_arg]}"
ew.write_event(event)


Expand Down