Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow configuring a dedicated metrics logger #978

Merged
merged 9 commits into from
Oct 4, 2022
48 changes: 46 additions & 2 deletions docs/implementation/logging.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
# Logging

Logs are configurable by the environment variables `<PLUGIN_NAME>_LOGLEVEL` (preferred) or `LOGLEVEL`. Use `LOGLEVEL` when you intend to control the log output for all taps and targets running within the environment. In contrast, we recommend setting `<PLUGIN_NAME>_LOGLEVEL` for more granual control of each tap or target individually.
Logs are configurable by the environment variables `<PLUGIN_NAME>_LOGLEVEL` (preferred)
or `LOGLEVEL`. Use `LOGLEVEL` when you intend to control the log output for all taps
and targets running within the environment. In contrast, we recommend setting
`<PLUGIN_NAME>_LOGLEVEL` for more granual control of each tap or target individually.

From most verbose to least verbose, the accepted values for logging level are `debug`, `info`, `warning`, and `error`. Logging level inputs are case insensitive.
From most verbose to least verbose, the accepted values for logging level are `debug`,
`info`, `warning`, and `error`. Logging level inputs are case-insensitive.

## Custom logging configuration

Users of a tap can configure the SDK logging by setting the `SINGER_SDK_LOG_CONFIG`
environment variable. The value of this variable should be a path to a YAML file in the
[Python logging dict format](https://docs.python.org/3/library/logging.config.html#dictionary-schema-details).

For example, to send [metrics](./metrics.md) (with logger name `singer_sdk.metrics`) to a file, you could use the following config:

```yaml
version: 1
disable_existing_loggers: false
formatters:
metrics:
format: "{asctime} {message}"
style: "{"
handlers:
metrics:
class: logging.FileHandler
formatter: metrics
filename: metrics.log
loggers:
singer_sdk.metrics:
level: INFO
handlers: [ metrics ]
propagate: yes
```

This will send metrics to a `metrics.log`:

```
2022-09-29 00:48:52,746 INFO METRIC: {"metric_type": "timer", "metric": "http_request_duration", "value": 0.501743, "tags": {"stream": "continents", "endpoint": "", "http_status_code": 200, "status": "succeeded"}}
2022-09-29 00:48:52,775 INFO METRIC: {"metric_type": "counter", "metric": "http_request_count", "value": 1, "tags": {"stream": "continents", "endpoint": ""}}
2022-09-29 00:48:52,776 INFO METRIC: {"metric_type": "timer", "metric": "sync_duration", "value": 0.7397160530090332, "tags": {"stream": "continents", "context": {}, "status": "succeeded"}}
2022-09-29 00:48:52,776 INFO METRIC: {"metric_type": "counter", "metric": "record_count", "value": 7, "tags": {"stream": "continents", "context": {}}}
2022-09-29 00:48:53,225 INFO METRIC: {"metric_type": "timer", "metric": "http_request_duration", "value": 0.392148, "tags": {"stream": "countries", "endpoint": "", "http_status_code": 200, "status": "succeeded"}}
2022-09-29 00:48:53,302 INFO METRIC: {"metric_type": "counter", "metric": "http_request_count", "value": 1, "tags": {"stream": "countries", "endpoint": ""}}
2022-09-29 00:48:53,302 INFO METRIC: {"metric_type": "timer", "metric": "sync_duration", "value": 0.5258760452270508, "tags": {"stream": "countries", "context": {}, "status": "succeeded"}}
2022-09-29 00:48:53,303 INFO METRIC: {"metric_type": "counter", "metric": "record_count", "value": 250, "tags": {"stream": "countries", "context": {}}}
```
20 changes: 12 additions & 8 deletions docs/implementation/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

Metrics logging is specified in the
[Singer Spec](https://hub.meltano.com/singer/spec#metrics). The SDK will automatically
emit two types of metrics `record_count` and `http_request_duration`.
emit metrics for `record_count`, `http_request_duration` and `sync_duration`.

Customization options:
## Customization options

Developers may optionally add a `metrics_log_level` config option to their taps,
which will automatically allow this metrics logging to be customized at runtime.
### `metrics_log_level`

When `metrics_log_level` is supported, users can then
set one of these values (case insensitive), `INFO`, `DEBUG`, `NONE`, to override the
default logging level for metrics. This can be helpful for REST-type sources which use
make a large number of REST calls can therefor have very noisy metrics.
Metrics are logged at the `INFO` level. Developers may optionally add a
`metrics_log_level` config option to their taps, `WARNING` or `ERROR` to disable
metrics logging.

### `SINGER_SDK_LOG_CONFIG`

Metrics are written by the `singer_sdk.metrics` logger, so the end user can set
`SINGER_SDK_LOG_CONFIG` to a logging config file that defines the format and output
for metrics. See the [logging docs](./logging.md) for an example file.

## Additional Singer Metrics References

Expand Down
1 change: 1 addition & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def mypy(session: Session) -> None:
"types-requests",
"types-pytz",
"types-simplejson",
"types-PyYAML",
)
session.run("mypy", *args)
if not session.posargs:
Expand Down
25 changes: 15 additions & 10 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ PyJWT = "~=2.4"
requests = "^2.25.1"
cryptography = ">=3.4.6,<39.0.0"
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
importlib-resources = {version = "^5.9.0", markers = "python_version < \"3.9\""}
memoization = ">=0.3.2,<0.5.0"
jsonpath-ng = "^1.5.3"
joblib = "^1.0.1"
Expand All @@ -54,6 +55,7 @@ typing-extensions = "^4.2.0"
simplejson = "^3.17.6"
jsonschema = "^4.16.0"
pytz = "^2022.2.1"
PyYAML = "^6.0"

# Sphinx dependencies installed as optional 'docs' extras
# https://github.com/readthedocs/readthedocs.org/issues/4912#issuecomment-664002569
Expand Down Expand Up @@ -89,6 +91,7 @@ types-python-dateutil = "^2.8.19"
types-pytz = "^2022.2.1.0"
types-requests = "^2.28.11"
types-simplejson = "^3.17.7"
types-PyYAML = "^6.0.12"
coverage = {extras = ["toml"], version = "^6.5"}

# Cookiecutter tests
Expand Down
15 changes: 15 additions & 0 deletions singer_sdk/default_logging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: 1
disable_existing_loggers: false
formatters:
console:
format: "{asctime} {message}"
style: "{"
handlers:
default:
class: logging.StreamHandler
formatter: console
stream: ext://sys.stderr
root:
level: INFO
propagate: true
handlers: [default]
24 changes: 24 additions & 0 deletions singer_sdk/helpers/_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations

import sys
from types import ModuleType

if sys.version_info >= (3, 9):
import importlib.resources as importlib_resources
from importlib.abc import Traversable
else:
import importlib_resources
from importlib_resources.abc import Traversable


def get_package_files(package: str | ModuleType) -> Traversable:
"""Load a file from a package.

Args:
package: The package to load the file from.
file: The file to load.

Returns:
The file as a Traversable object.
"""
return importlib_resources.files(package)
Loading