GithubHelp home page GithubHelp logo

mweinelt / kea-exporter Goto Github PK

View Code? Open in Web Editor NEW
31.0 7.0 17.0 96 KB

Export Kea Metrics in the Prometheus Exposition Format

License: MIT License

Python 93.29% Nix 4.30% Dockerfile 2.40%
kea dhcp prometheus-exporter prometheus metrics hacktoberfest

kea-exporter's Introduction

GitHub license GitHub tag (latest SemVer) PyPI - Version PyPI - Downloads

kea-exporter

Prometheus Exporter for the ISC Kea DHCP Server.

From v0.4.0 on Kea >=1.3.0 is required, as the configuration, specifically subnet information, will be read from the control socket.

Installation

Package versions via repology.org

The latest stable version can always be installed from PyPi:

$ pip install kea-exporter

and upgraded with:

$ pip install --upgrade kea-exporter

Docker

A docker image is available and can be configured with environment variables see usage section

$ docker pull ghcr.io/mweinelt/kea-exporter

Features

  • DHCP4 & DHCP6 Metrics (tested against Kea 2.4.1)
  • Configuration and statistics via control socket or http api

Currently not working:

  • Automatic config reload (through inotify)

Known Limitations

The following features are not supported yet, help is welcome.

  • Shared Networks
  • Custom Subnet Identifiers

Usage

Pass one or multiple Unix Domain Socket path or HTTP Control-Agent URLs to the kea-exporter executable. All other options are optional.

Usage: python -m kea_exporter [OPTIONS] TARGETS...

Options:
  -a, --address TEXT      Address that the exporter binds to.
  -p, --port INTEGER      Port that the exporter binds to.
  -i, --interval INTEGER  Minimal interval between two queries to Kea in
                          seconds.
  --client-cert PATH      Path to client certificate used to in HTTP requests
  --client-key PATH       Path to client key used in HTTP requests
  --version               Show the version and exit.
  --help                  Show this message and exit.

You can also configure the exporter using environment variables:

export ADDRESS="0.0.0.0"
export PORT="9547"
export INTERVAL="7.5"
export TARGETS="http://router.example.com:8000"
export CLIENT_CERT="/etc/kea-exporter/client.crt"
export CLIENT_KEY="/etc/kea-exporter/client.key"

Configure Control Socket

The exporter uses Kea's control socket to request both configuration and statistics. Consult the documentation on how to set up the control socket:

HTTPS

If you need to validate a self-signed certificate on a Kea instance, you can set REQUESTS_CA_BUNDLE environment variable to a bundle CA path.

Permissions

Kea Exporter needs to be able to read and write on the socket, hence it's permissions might need to be modified accordingly.

Grafana-Dashboard

A dashboard for this exporter is available at https://grafana.com/grafana/dashboards/12688.

kea-exporter's People

Contributors

m0nsterrr avatar mweinelt avatar qwiko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

kea-exporter's Issues

Reasoning for stork alternative

Hello. Thanks for the work in creating this excellent exporter. I am curious about the reasoning for creating and using this vs. stork / stork-agent? It might be helpful to include some of this knowledge in the README. Thank you.

Python version

Hi,
I'm having various problems getting the kea-exporter running. Which Python version are you using?

Add subnet ID label to metrics

Would be useful to include. I think most users will be specifying their own rather than allow Kea to auto-assign, as otherwise config changes can cause problems. If others are like us, the chosen subnet IDs are more meaningful than CIDRs.

Exporter silently exits with Broken Pipe when sending to unix domain socket

>>> import json
>>> import socket
>>> msg = bytes(json.dumps({'command': 'statistic-get-all'}), 'utf-8')
>>> sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
>>> sock.connect("/run/kea-dhcp4.sock")
>>> sock.send(msg)
32
>>> sock.recv(8192)
[...]
>>> sock.send(msg)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
BrokenPipeError: [Errno 32] Broken pipe

The BrokenPipeError is propagated into the kea-exporter but we don't see a traceback. It just silently exits the exporter. This happens on the second send, the first is fine.

Seen on Debian Stretch with Kea 1.3.0, works with Kea 1.1.0.

Automatic configuration update via control socket

In #2 there was a remark that the mtime on the socket would change when the configuration was updated. This did not hold true, so since fetching the config from the socket we lost support for automatic updates.

Kea HTTP ctrl-agent support

I think it would be a good idea to continue developing this repository.
I would like to help rebuilding to support both sockets and http "backends".
I see someone have already implemented a http-version but it is rather outdated.
https://github.com/ddericco/kea_exporter/tree/master

I can also see that a docker container with http metric support would be very useful for kubernetes deployments with automatic prometheus discovery. Configurable either with environment-variables or a configmap/configfile.

Wrong value calculation on DHCP pool

In my setup, I have a subnet (192.168.50.0/24) with a limited DHCP range (192.168.50.201-192.168.50.240). Additionally, I have static DHCP entries that are not within the DHCP range for IoT devices that cannot have static IP addresses configured. The issue is that the two pools have the same values when they should not.

kea-leases.csv

192.168.50.101,98:da:c4:be:e8:89,01:98:da:c4:be:e8:89,4000,1710894279,3,0,0,hs110-rack1,0,,0
192.168.50.102,98:da:c4:be:e8:96,01:98:da:c4:be:e8:96,4000,1710894318,3,0,0,hs110-rack2,0,,0
192.168.50.103,98:da:c4:be:e9:ea,01:98:da:c4:be:e9:ea,4000,1710894167,3,0,0,hs110-chambre1,0,,0
192.168.50.104,48:e1:e9:dc:96:1e,,4000,1710893796,3,0,0,meross-tireuse,0,,0
192.168.50.105,48:e1:e9:dc:9e:a5,,4000,1710893811,3,0,0,meross-monsieur-cuisine,0,,0
192.168.50.111,04:cf:8c:b5:1e:17,,4000,1710893677,3,0,0,xiaomi-bulb-chambre1,0,,0
192.168.50.112,64:90:c1:6a:f0:98,01:64:90:c1:6a:f0:98,4000,1710894159,3,0,0,xiaomi-vaccum,0,,0
192.168.50.113,ec:4d:3e:28:c2:23,,4000,1710894244,3,0,0,xiaomi-bulb-salon,0,,0
192.168.50.114,b4:60:ed:45:50:00,,4000,1710894056,3,0,0,xiaomi-light-bar,0,,0
192.168.50.115,58:b6:23:ed:62:ce,,4000,1710893882,3,0,0,xiaomi-led-chambre1,0,,0
192.168.50.116,58:b6:23:ed:8a:df,,4000,1710893910,3,0,0,xiaomi-led-bar,0,,0
192.168.50.117,ec:4d:3e:56:b5:9d,01:ec:4d:3e:56:b5:9d,4000,1710893826,3,0,0,xiaomi-lamp-salon,0,,0
192.168.50.118,b4:60:ed:13:99:f9,01:b4:60:ed:13:99:f9,4000,1710894043,3,0,0,xiaomi-lamp-cuisine,0,,0
192.168.50.119,ec:4d:3e:2a:c7:18,,4000,1710894063,3,0,0,xiaomi-bulb-entree,0,,0
192.168.50.201,c8:2a:dd:8f:13:62,01:c8:2a:dd:8f:13:62,4000,1710892943,3,0,0,pixel-7,0,,0
192.168.50.202,44:5c:e9:51:14:10,01:44:5c:e9:51:14:10,4000,1710893007,3,0,0,samsung,0,,0
192.168.50.217,48:b0:2d:2d:9c:09,01:48:b0:2d:2d:9c:09,4000,1710893670,3,0,0,,0,,0
192.168.50.220,42:6c:fe:7a:fb:ea,01:42:6c:fe:7a:fb:ea,4000,1710893882,3,0,0,,0,,0
192.168.50.231,9c:28:41:09:42:9b,,4000,1710893356,3,0,0,mcsmartmcsmart30202123y3l625,0,,0

The second metric should have a value of 5

kea_dhcp4_addresses_assigned_total{pool="",subnet="192.168.50.0/24",subnet_id="3"} 19.0
kea_dhcp4_addresses_assigned_total{pool="192.168.50.201-192.168.50.240",subnet="192.168.50.0/24",subnet_id="3"} 19.0

The first metric should have a value of 254

kea_dhcp4_addresses_total{pool="",subnet="192.168.50.0/24",subnet_id="3"} 40.0
kea_dhcp4_addresses_total{pool="192.168.50.201-192.168.50.240",subnet="192.168.50.0/24",subnet_id="3"} 40.0

Make interval optional

Query kea only when asked, so the timestamping on the data in Prometheus is correct.

This means dropping interval by default.

Some unhandled metrics

  • pool - subnet[id].pool[pid] has a bunch of per-pool stats
  • v4-lease-reuses - Number of times an IPv4 lease had its CLTT increased in memory and its expiration time left unchanged in persistent storage as part of the lease caching feature. This is referred to as a lease reuse. This statistic is global.
  • v6-ia-na-lease-reuses - Number of times an IA_NA lease had its CLTT increased in memory and its expiration time left unchanged in persistent storage as part of the lease caching feature. This is referred to as a lease reuse. This statistic is global.
  • v6-ia-pd-lease-reuses - Number of times an IA_PD lease had its CLTT increased in memory and its expiration time left unchanged in persistent storage as part of the lease caching feature. This is referred to as a lease reuse. This statistic is global.

Support failed allocation metrics in 2.2.0

2. **New statistics**: New statistics were have been added to kea-dhcp4 
to counts cases of host reservation conflicts. They are tracked at both 
the global and subnet level as `v4-reservation-conflicts` and 
`subnet[id].v4-reservation-conflicts`, respectively [#2419].
9. **New statistics for failed allocations**. If the class requirements 
for your address pools are defined too tightly, it is possible that some 
clients will not get an address. To ease the investigation of this 
problem, many new statistics were added:
`v4-allocation-fail`, `v4-allocation-fail-shared-network`, 
`v4-allocation-fail-subnet`, `v4-allocation-fail-no-pools`, 
`v4-allocation-fail-classes`, `subnet[X].v4-allocation-fail`, 
`subnet[X].v4-allocation-fail-shared-network`, 
`subnet[X].v4-allocation-fail-subnet`, 
`subnet[X].v4-allocation-fail-no-pools`, 
`subnet[X].v4-allocation-fail-classes`,
`v6-allocation-fail`, `v6-allocation-fail-shared-network`, 
`v6-allocation-fail-subnet`, `v6-allocation-fail-no-pools`, 
`v6-allocation-fail-classes`, `subnet[X].v6-allocation-fail`, 
`subnet[X].v6-allocation-fail-shared-network`, 
`subnet[X].v6-allocation-fail-subnet`, 
`subnet[X].v6-allocation-fail-no-pools`, and 
`subnet[X].v6-allocation-fail-classes` [#2054].

https://downloads.isc.org/isc/kea/2.2.0/Kea-2.2.0-ReleaseNotes.txt

Fails to start with `ValueError: Incorrect label names`

Running the latest tag (v0.5.0) I get this error on startup:

kea-exporter[10555]: Listening on http://0.0.0.0:9547
kea-exporter[10555]: Traceback (most recent call last):
kea-exporter[10555]:   File "/nix/store/cms5b30wjy5y96zwnspki4lh4zsnv1gh-kea-exporter-0.5.0/bin/.kea-exporter-wrapped", line 9, in <module>
kea-exporter[10555]:     sys.exit(cli())
kea-exporter[10555]:   File "/nix/store/apiha8lds5nbj1039kxj90vc4xr5q0sf-python3.10-click-8.1.3/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
kea-exporter[10555]:     return self.main(*args, **kwargs)
kea-exporter[10555]:   File "/nix/store/apiha8lds5nbj1039kxj90vc4xr5q0sf-python3.10-click-8.1.3/lib/python3.10/site-packages/click/core.py", line 1055, in main
kea-exporter[10555]:     rv = self.invoke(ctx)
kea-exporter[10555]:   File "/nix/store/apiha8lds5nbj1039kxj90vc4xr5q0sf-python3.10-click-8.1.3/lib/python3.10/site-packages/click/core.py", line 1404, in invoke
kea-exporter[10555]:     return ctx.invoke(self.callback, **ctx.params)
kea-exporter[10555]:   File "/nix/store/apiha8lds5nbj1039kxj90vc4xr5q0sf-python3.10-click-8.1.3/lib/python3.10/site-packages/click/core.py", line 760, in invoke
kea-exporter[10555]:     return __callback(*args, **kwargs)
kea-exporter[10555]:   File "/nix/store/cms5b30wjy5y96zwnspki4lh4zsnv1gh-kea-exporter-0.5.0/lib/python3.10/site-packages/kea_exporter/cli.py", line 22, in cli
kea-exporter[10555]:     exporter.update()
kea-exporter[10555]:   File "/nix/store/cms5b30wjy5y96zwnspki4lh4zsnv1gh-kea-exporter-0.5.0/lib/python3.10/site-packages/kea_exporter/kea.py", line 637, in update
kea-exporter[10555]:     metric.labels(**labels).set(value)
kea-exporter[10555]:   File "/nix/store/zcrn80lfchkiggn63yvjj6cm0pz213rv-python3.10-prometheus-client-0.14.1/lib/python3.10/site-packages/prometheus_client/metrics.py", >
kea-exporter[10555]:     raise ValueError('Incorrect label names')
kea-exporter[10555]: ValueError: Incorrect label names
systemd[1]: prometheus-kea-exporter.service: Main process exited, code=exited, status=1/FAILURE

Adding v6-allocation-fail-no-pools, v6-allocation-fail-shared-network, v6-allocation-fail-classes, & v6-allocation-fail-subnet to the metric_dhcp6_subnet_ignore list does cause the exporter to not crash. Was adding these to the ignore list part of the proposed changes from #26? Let me know if seeing my dhcp6 config would be helpful.

KeyError: 'cumulative-assigned-addresses

How to launch kea-exporter?

example 1

[root@southpark kea_exporter]# kea-exporter --address "127.0.0.1" --port 8001 /tmp/kea4-ctrl-socket
Listening on http://127.0.0.1:8001
Traceback (most recent call last):
  File "/usr/local/bin/kea-exporter", line 11, in <module>
    sys.exit(cli())
  File "/usr/local/lib/python3.6/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.6/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.6/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/kea_exporter/cli.py", line 22, in cli
    exporter.update()
  File "/usr/local/lib/python3.6/site-packages/kea_exporter/kea.py", line 502, in update
    metric_info = self.metrics_dhcp4_map[key]
KeyError: 'cumulative-assigned-addresses'

example2

[root@southpark kea_exporter]# kea-exporter --address "localhost" --port 8001 /tmp/kea4-ctrl-socket
Listening on http://localhost:8001
Traceback (most recent call last):
  File "/usr/local/bin/kea-exporter", line 11, in <module>
    sys.exit(cli())
  File "/usr/local/lib/python3.6/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.6/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.6/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/kea_exporter/cli.py", line 22, in cli
    exporter.update()
  File "/usr/local/lib/python3.6/site-packages/kea_exporter/kea.py", line 502, in update
    metric_info = self.metrics_dhcp4_map[key]
KeyError: 'cumulative-assigned-addresses

I would be grateful for an example.

inotify wants str instead of bytes

15:04 < hexa- > hm, dann will das inotify package bei dir nun einen str haben, statt bytes

ffadmin@gw03:~$ /home/ffadmin/.local/bin/kea-exporter /etc/kea/kea-dhcp4.conf
Traceback (most recent call last):
  File "/home/ffadmin/.local/bin/kea-exporter", line 11, in <module>
    sys.exit(cli())
  File "/home/ffadmin/.local/lib/python3.5/site-packages/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/home/ffadmin/.local/lib/python3.5/site-packages/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/home/ffadmin/.local/lib/python3.5/site-packages/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/ffadmin/.local/lib/python3.5/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/home/ffadmin/.local/lib/python3.5/site-packages/kea_exporter/cli.py", line 17, in cli
    exporter = KeaExporter(config)
  File "/home/ffadmin/.local/lib/python3.5/site-packages/kea_exporter/kea.py", line 54, in __init__
    bytes(config_path, 'utf-8'), mask=inotify.constants.IN_MODIFY
  File "/home/ffadmin/.local/lib/python3.5/site-packages/inotify/adapters.py", line 93, in add_watch
    path_bytes = path_unicode.encode('utf8')
AttributeError: 'bytes' object has no attribute 'encode'

Follow click.echo api

click echo api specifies that message should be one argument.

From click documentation: https://click.palletsprojects.com/en/8.1.x/api/#click.echo

click.echo(message=None, file=None, nl=True, err=False, color=None)

We should change for example this to have message be one argument instead of two.

click.echo(
    "Ignoring metric because subnet vanished from configuration:",
    f"\tdhcp_version: {dhcp_version.name}, subnet_id: {subnet_id}",
    file=sys.stderr,
)

We get this error when not following the api

Traceback (most recent call last):
  File "/usr/local/bin/kea-exporter", line 8, in <module>
    sys.exit(cli())
  File "/usr/local/lib/python3.10/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.10/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.10/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
  File "/usr/src/app/kea_exporter/cli.py", line 57, in cli
    exporter.update()
  File "/usr/src/app/kea_exporter/kea_http_exporter.py", line 70, in update
    self.parse_metrics(dhcp_version, arguments, subnets)
  File "/usr/src/app/kea_exporter/base_exporter.py", line 501, in parse_metrics
    click.echo(
TypeError: echo() got multiple values for argument 'file'

Test against older version fixtures

Collect test fixtures, so we can always test against the output of older releases to achieve a higher confidence.

The parsing logic is the same for UDS/HTTP, and we could just set up a StubExporter, where we pipe the fixtures.

Evaluate shared networks

Found a patch on a downstream fork, let's check if it satisfies our understanding of shared networks and integrate it.

From ab7475a6dc1d93f479f0a827742bb5ce584f481a Mon Sep 17 00:00:00 2001
From: masem <[email protected]>
Date: Mon, 15 Jun 2020 16:05:51 +0200
Subject: [PATCH] Added shared_network label to exported prom subnet stats

---
 kea_exporter/kea.py | 40 ++++++++++++++++++++++++++++------------
 1 file changed, 28 insertions(+), 12 deletions(-)

diff --git a/kea_exporter/kea.py b/kea_exporter/kea.py
index 502544b..2485875 100644
--- a/kea_exporter/kea.py
+++ b/kea_exporter/kea.py
@@ -59,9 +59,23 @@ def reload(self):
         if 'Dhcp4' in self.config:
             self.dhcp_version = DHCPVersion.DHCP4
             subnets = self.config['Dhcp4']['subnet4']
+            for shared_network in self.config['Dhcp4']['shared-networks']:
+                shared_network4 = []
+                for subnet4 in shared_network['subnet4']:
+                    subnet4['shared_network'] = shared_network['name']
+                    shared_network4.append(subnet4)
+                subnets += shared_network4
+
         elif 'Dhcp6' in self.config:
             self.dhcp_version = DHCPVersion.DHCP6
             subnets = self.config['Dhcp6']['subnet6']
+            for shared_network in self.config['Dhcp6']['shared-networks']:
+                shared_network6 = []
+                for subnet6 in shared_network['subnet6']:
+                    subnet6['shared_network'] = shared_network['name']
+                    shared_network6.append(subnet6)
+                subnets += shared_network6
+
         else:
             click.echo(f'Socket {self.sock_path} has no supported configuration', file=sys.stderr)
             sys.exit(1)
@@ -109,23 +123,23 @@ def setup_dhcp4_metrics(self):
             'addresses_assigned_total': Gauge(
                 f'{self.prefix_dhcp4}_addresses_assigned_total',
                 'Assigned addresses',
-                ['subnet']),
+                ['subnet', 'network']),
             'addresses_declined_total': Gauge(
                 f'{self.prefix_dhcp4}_addresses_declined_total',
                 'Declined counts',
-                ['subnet']),
+                ['subnet', 'network']),
             'addresses_declined_reclaimed_total': Gauge(
                 f'{self.prefix_dhcp4}_addresses_declined_reclaimed_total',
                 'Declined addresses that were reclaimed',
-                ['subnet']),
+                ['subnet', 'network']),
             'addresses_reclaimed_total': Gauge(
                 f'{self.prefix_dhcp4}_addresses_reclaimed_total',
                 'Expired addresses that were reclaimed',
-                ['subnet']),
+                ['subnet', 'network']),
             'addresses_total': Gauge(
                 f'{self.prefix_dhcp4}_addresses_total',
                 'Size of subnet address pool',
-                ['subnet']
+                ['subnet', 'network']
             )
         }
 
@@ -278,36 +292,36 @@ def setup_dhcp6_metrics(self):
             'addresses_declined_total': Gauge(
                 f'{self.prefix_dhcp6}_addresses_declined_total',
                 'Declined addresses',
-                ['subnet']),
+                ['subnet', 'network']),
             'addresses_declined_reclaimed_total': Gauge(
                 f'{self.prefix_dhcp6}_addresses_declined_reclaimed_total',
                 'Declined addresses that were reclaimed',
-                ['subnet']),
+                ['subnet', 'network']),
             'addresses_reclaimed_total': Gauge(
                 f'{self.prefix_dhcp6}_addresses_reclaimed_total',
                 'Expired addresses that were reclaimed',
-                ['subnet']),
+                ['subnet', 'network']),
 
             # IA_NA
             'na_assigned_total': Gauge(
                 f'{self.prefix_dhcp6}_na_assigned_total',
                 'Assigned non-temporary addresses (IA_NA)',
-                ['subnet']),
+                ['subnet', 'network']),
             'na_total': Gauge(
                 f'{self.prefix_dhcp6}_na_total',
                 'Size of non-temporary address pool',
-                ['subnet']
+                ['subnet', 'network']
             ),
 
             # IA_PD
             'pd_assigned_total': Gauge(
                 f'{self.prefix_dhcp6}_pd_assigned_total',
                 'Assigned prefix delegations (IA_PD)',
-                ['subnet']),
+                ['subnet', 'network']),
             'pd_total': Gauge(
                 f'{self.prefix_dhcp6}_pd_total',
                 'Size of prefix delegation pool',
-                ['subnet']
+                ['subnet', 'network']
             ),
 
         }
@@ -494,6 +508,8 @@ def update(self):
                                 )
                             continue
                         labels['subnet'] = subnet['subnet']
+                        labels['network'] = subnet['shared_network']                                              
+                                                
                     else:
                         click.echo(f'subnet pattern failed for metric: {key}',
                                    file=sys.stderr)

via masem@ab7475a

Add debug-logging

It would be useful in developing and debugging to add some kind of logging-engine with different levels that is configurable for users with an argument or environment-variable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.