Skip to content

Commit

Permalink
Merge pull request #274 from venkatbvc/log_to_file_with_rotation
Browse files Browse the repository at this point in the history
Add support for file-based configuration of logging

increments #minor
  • Loading branch information
jekkel authored Jul 13, 2023
2 parents 59eb1f5 + 8e9a8fc commit ccaf6ff
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 13 deletions.
16 changes: 12 additions & 4 deletions .github/workflows/build_and_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ jobs:
wait_for_pod_ready "sidecar"
wait_for_pod_ready "sidecar-5xx"
wait_for_pod_ready "sidecar-pythonscript"
wait_for_pod_ready "sidecar-pythonscript-logfile"
wait_for_pod_ready "sidecar-logtofile-pythonscript"
wait_for_pod_ready "dummy-server-pod"
- name: Install Configmaps and Secrets
Expand All @@ -102,21 +104,22 @@ jobs:
sleep 20
echo "Installing resources..."
kubectl apply -f "test/resources/resources.yaml"
pods=("sidecar" "sidecar-5xx" "sidecar-pythonscript")
pods=("sidecar" "sidecar-5xx" "sidecar-pythonscript" "sidecar-pythonscript-logfile")
resources=("sample-configmap" "sample-secret-binary" "absolute-configmap" "relative-configmap" "change-dir-configmap" "similar-configmap-secret" "url-configmap-500" "url-configmap-basic-auth" "sample-configmap")
for p in ${pods[*]}; do
for r in ${resources[*]}; do
wait_for_pod_log $p $r
done
done
# 5 more seconds after the last thing appeared in the logs.
sleep 5
# 10 more seconds after the last thing appeared in the logs.
sleep 10
- name: Retrieve pod logs
run: |
mkdir /tmp/logs
kubectl logs sidecar > /tmp/logs/sidecar.log
kubectl logs sidecar-5xx > /tmp/logs/sidecar-5xx.log
kubectl logs sidecar-pythonscript > /tmp/logs/sidecar-pythonscript.log
kubectl logs sidecar-pythonscript-logfile > /tmp/logs/sidecar-pythonscript-logfile.log
kubectl logs dummy-server-pod > /tmp/logs/dummy-server.log
- name: Upload artifacts (pod logs)
uses: actions/upload-artifact@v3
Expand Down Expand Up @@ -215,7 +218,12 @@ jobs:
- name: Verify sidecar-python logs after initial sync
run: |
# Make sure to update this number this when adding or removing configmap or secrets
test $(cat /tmp/logs/sidecar-pythonscript.log | grep "Hello from python script!" | wc -l) = "9"
# For log to a file, Need to consider Jobs "Install Configmaps and Secrets" and "Update Configmaps and Secrets"
# Total is (9 + 7)
test $(cat /tmp/logs/sidecar-pythonscript.log | grep "Hello from python script!" | wc -l) = "9" &&
test $(cat /tmp/logs/sidecar-pythonscript-logfile.log | grep "Hello from python script!" | wc -l) = "9" &&
kubectl exec sidecar-logtofile-pythonscript -- sh -c "test -e /opt/logs/sidecar.log" &&
test $(kubectl exec sidecar-logtofile-pythonscript -- sh -c 'cat /opt/logs/sidecar.log | grep "Hello from python script!" | wc -l') = "16"
- name: Verify sidecar files after update
run: |
kubectl exec sidecar -- sh -c "ls /tmp/" &&
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ If the filename ends with `.url` suffix, the content will be processed as a URL
| `SCRIPT` | Absolute path to a script to execute after a configmap got reloaded. It runs before calls to `REQ_URI`. If the file is not executable it will be passed to `sh`. Otherwise it's executed as is. [Shebangs](https://en.wikipedia.org/wiki/Shebang_(Unix)) known to work are `#!/bin/sh` and `#!/usr/bin/env python` | false | - | string |
| `ERROR_THROTTLE_SLEEP` | How many seconds to wait before watching resources again when an error occurs | false | `5` | integer |
| `SKIP_TLS_VERIFY` | Set to `true` to skip tls verification for kube api calls | false | - | boolean |
| `REQ_SKIP_TLS_VERIFY` | Set to `true` to skip tls verification for all HTTP requests (except the Kube API server, which are controlled by `SKIP_TLS_VERIFY`). Note that the latest 'requests' library no longer offer a way to disable this via env vars; however a custom truststore can be set via REQUESTS_CA_BUNDLE. | false | - | boolean |
| `REQ_SKIP_TLS_VERIFY` | Set to `true` to skip tls verification for all HTTP requests (except the Kube API server, which are controlled by `SKIP_TLS_VERIFY`). Note that the latest 'requests' library no longer offer a way to disable this via env vars; however a custom truststore can be set via REQUESTS_CA_BUNDLE. | false | - | boolean |
| `UNIQUE_FILENAMES` | Set to true to produce unique filenames where duplicate data keys exist between ConfigMaps and/or Secrets within the same or multiple Namespaces. | false | `false` | boolean |
| `DEFAULT_FILE_MODE` | The default file system permission for every file. Use three digits (e.g. '500', '440', ...) | false | - | string |
| `KUBECONFIG` | if this is given and points to a file or `~/.kube/config` is mounted k8s config will be loaded from this file, otherwise "incluster" k8s configuration is tried. | false | - | string |
Expand All @@ -92,3 +92,4 @@ If the filename ends with `.url` suffix, the content will be processed as a URL
| `LOG_LEVEL` | Set the logging level. (DEBUG, INFO, WARN, ERROR, CRITICAL) | false | `INFO` | string |
| `LOG_FORMAT` | Set a log format. (JSON or LOGFMT) | false | `JSON` | string |
| `LOG_TZ` | Set the log timezone. (LOCAL or UTC) | false | `LOCAL` | string |
| `LOG_CONFIG` | Log configuration file path. If not configured, uses the default log config for backward compatibility support. When not configured `LOG_LEVEL, LOG_FORMAT and LOG_TZ` would be used. Refer to [Python logging](https://docs.python.org/3/library/logging.config.html) for log configuration. For sample configuration file refer to file examples/example_logconfig.yaml | false | - | string |
File renamed without changes.
33 changes: 33 additions & 0 deletions examples/example_logconfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: 1
disable_existing_loggers: false

root:
level: DEBUG
handlers: [console]

handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: JSON

formatters:
JSON:
(): logger.JsonFormatter
format: '%(levelname)s %(message)s'
rename_fields: {
"message": "msg",
"levelname": "level"
}
LOGFMT:
(): logger.LogfmtFormatter
keys: [
"time",
"level",
"msg"
]
mapping: {
"time": "asctime",
"level": "levelname",
"msg": "message"
}
74 changes: 66 additions & 8 deletions src/logger.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import logging
import os
import sys
import yaml
from datetime import datetime
from typing import Optional

from dateutil.tz import tzlocal, tzutc
from logfmter import Logfmter
from pythonjsonlogger import jsonlogger
from logging import config

# Supported Timezones for time format (in ISO 8601)
LogTimezones = {
Expand All @@ -17,6 +20,7 @@
level = os.getenv("LOG_LEVEL", logging.INFO)
fmt = os.getenv("LOG_FORMAT", 'JSON')
tz = os.getenv("LOG_TZ", 'LOCAL')
log_conf_file = os.getenv("LOG_CONFIG","")

log_tz = LogTimezones[tz.upper()] if LogTimezones.get(tz.upper()) else LogTimezones['LOCAL']

Expand Down Expand Up @@ -59,16 +63,70 @@ def add_fields(self, log_record, record, message_dict):
mapping={"time": "asctime", "level": "levelname", "msg": "message"}))
}

logLevel = level.upper() if isinstance(level, str) else level
log_fmt = LogFormatters[fmt.upper()] if LogFormatters.get(fmt.upper()) else LogFormatters['JSON']

# Initialize/configure root logger
root_logger = logging.getLogger()
log_handler = logging.StreamHandler()
log_handler.setFormatter(log_fmt)
root_logger.addHandler(log_handler)
root_logger.setLevel(level.upper() if isinstance(level, str) else level)
root_logger.addHandler(log_handler)
default_log_config = {
"version": 1,
"disable_existing_loggers": False,
"root": {
"level": logLevel,
"handlers": [
"console"
]
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": logLevel,
"formatter": fmt.upper()
}
},
"formatters": {
"JSON": {
"()": "logger.JsonFormatter",
"format": "%(levelname)s %(message)s",
"rename_fields": {
"message": "msg",
"levelname": "level"
}
},
"LOGFMT": {
"()": "logger.LogfmtFormatter",
"keys": [
"time",
"level",
"msg"
],
"mapping": {
"time": "asctime",
"level": "levelname",
"msg": "message"
}
}
}
}

def get_log_config():
if log_conf_file != "" :
try:
with open(log_conf_file, 'r') as stream:
config = yaml.load(stream, Loader=yaml.FullLoader)
return config
except FileNotFoundError:
msg = "Config file: "+ log_conf_file + " Not Found"
print(msg)
sys.exit(1)
except yaml.YAMLError as e:
print("Error loading yaml file:")
print(e)
sys.exit(2)
else:
return default_log_config

# Initialize/configure root logger
log_config = get_log_config()
config.dictConfig(log_config)

def get_logger():
return logging.getLogger('k8s-sidecar')
return logging.getLogger('k8s-sidecar')
2 changes: 2 additions & 0 deletions src/sidecar.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
from requests.packages.urllib3.util.retry import Retry

from helpers import REQ_RETRY_TOTAL, REQ_RETRY_CONNECT, REQ_RETRY_READ, REQ_RETRY_BACKOFF_FACTOR

from logger import get_logger
from resources import list_resources, watch_for_changes, prepare_payload


METHOD = "METHOD"
UNIQUE_FILENAMES = "UNIQUE_FILENAMES"
SKIP_TLS_VERIFY = "SKIP_TLS_VERIFY"
Expand Down
155 changes: 155 additions & 0 deletions test/resources/sidecar.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,161 @@ spec:
defaultMode: 0777
---
apiVersion: v1
kind: ConfigMap
metadata:
name: logger-config
labels:
somelabel: "somesome"
data:
log_conf.yaml: |-
version: 1
disable_existing_loggers: false
root:
level: DEBUG
handlers: [console]
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: JSON
formatters:
JSON:
(): logger.JsonFormatter
format: '%(levelname)s %(message)s'
rename_fields: {
"message": "msg",
"levelname": "level"
}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: logger-config-tofile
labels:
somelabel: "somesome"
data:
log_conf.yaml: |-
version: 1
disable_existing_loggers: false
root:
level: DEBUG
handlers: [console]
handlers:
console:
class: logging.handlers.RotatingFileHandler
level: DEBUG
formatter: JSON
filename: "/opt/logs/sidecar.log"
maxBytes: 2097152
backupCount: 3
formatters:
JSON:
(): logger.JsonFormatter
format: '%(levelname)s %(message)s'
rename_fields: {
"message": "msg",
"levelname": "level"
}
---
apiVersion: v1
kind: Pod
metadata:
name: sidecar-pythonscript-logfile
namespace: default
spec:
serviceAccountName: sample-acc
containers:
- name: sidecar
image: kiwigrid/k8s-sidecar:testing
volumeMounts:
- name: shared-volume
mountPath: /tmp/
- name: script-volume
mountPath: /opt/script.py
subPath: script.py
- name: log-config
mountPath: /etc/k8s-sidecar
env:
- name: LABEL
value: "findme"
- name: FOLDER
value: /tmp/
- name: RESOURCE
value: both
- name: SCRIPT
value: "/opt/script.py"
- name: LOG_LEVEL
value: "DEBUG"
- name: LOG_CONFIG
value: "/etc/k8s-sidecar/log_conf.yaml"
volumes:
- name: shared-volume
emptyDir: { }
- name: script-volume
configMap:
name: script-configmap
defaultMode: 0777
- name: log-config
configMap:
name: logger-config
defaultMode: 0777

---
apiVersion: v1
kind: Pod
metadata:
name: sidecar-logtofile-pythonscript
namespace: default
spec:
serviceAccountName: sample-acc
containers:
- name: sidecar
image: kiwigrid/k8s-sidecar:testing
volumeMounts:
- name: shared-volume
mountPath: /tmp/
- name: script-volume
mountPath: /opt/script.py
subPath: script.py
- name: log-config
mountPath: /etc/k8s-sidecar
- name: logdir
mountPath: /opt/logs
env:
- name: LABEL
value: "findme"
- name: FOLDER
value: /tmp/
- name: RESOURCE
value: both
- name: SCRIPT
value: "/opt/script.py"
- name: LOG_LEVEL
value: "DEBUG"
- name: LOG_CONFIG
value: "/etc/k8s-sidecar/log_conf.yaml"
volumes:
- name: shared-volume
emptyDir: { }
- name: logdir
emptyDir: { }
- name: script-volume
configMap:
name: script-configmap
defaultMode: 0777
- name: log-config
configMap:
name: logger-config-tofile
defaultMode: 0777

---
apiVersion: v1
kind: Pod
metadata:
name: dummy-server-pod
Expand Down

0 comments on commit ccaf6ff

Please sign in to comment.