From 58da7bfbf8e7c247dfe13a0aa634c72c40ee476e Mon Sep 17 00:00:00 2001 From: vfabi Date: Thu, 28 Mar 2024 00:57:25 +0200 Subject: [PATCH] INIT --- CHANGELOG.md | 7 ++ LICENSE | 201 ++++++++++++++++++++++++++++++++++ NOTICE | 2 + README.md | 75 +++++++++++++ python_app_logger/__init__.py | 1 + python_app_logger/main.py | 164 +++++++++++++++++++++++++++ setup.py | 41 +++++++ 7 files changed, 491 insertions(+) create mode 100755 CHANGELOG.md create mode 100755 LICENSE create mode 100755 NOTICE create mode 100755 README.md create mode 100644 python_app_logger/__init__.py create mode 100644 python_app_logger/main.py create mode 100644 setup.py diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..842f3b0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +## [1.0.0] - 2024-03-28 + +### Added + +### Changed + +### Fixed diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..596b0e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Vadim Fabi + + 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. diff --git a/NOTICE b/NOTICE new file mode 100755 index 0000000..a12cbba --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +Copyright 2020 Vadim Fabi +Licensed under the Apache License, Version 2.0 diff --git a/README.md b/README.md new file mode 100755 index 0000000..3d771f4 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# python-app-logger +![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/vfabi/python-app-logger) +![GitHub last commit](https://img.shields.io/github/last-commit/vfabi/python-app-logger) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +A custom python applications logging handler. Use custom JSON format and sends logs via Telegram Bot Api. + +## Features +- Custom stream JSON logging handler +- Custom Telegram logging handler +- Telegram logging handler loglevel (severity) routing + + +# Requirements and dependencies +- https://github.com/vfabi/python-telegram-handler + + +# Usage + +## Setup +Just run: `pip install git+https://github.com/vfabi/python-app-logger` + +## Application integration +`get_loger` function arguments: + +``` + app_name (str): application name. (MANDATORY) + app_version (str): application version. (MANDATORY) + app_environment (str): application environment. (MANDATORY) + loglevel (str): loglevel (severity). (OPTIONAL) + logger_name (str): logger name. (OPTIONAL) + telegram_bot_id (str): Telegram bot id. (OPTIONAL) + telegram_chat_ids (dict): dict with severity - Telegram chat id mapping. Example: {'debug': '1234567890', 'info': '22334455', 'critical': '9988776655'}. (OPTIONAL) +``` + +Example: + +```python +from python_app_logger import get_logger + +# Set logger configuration +logger = get_logger( + app_name='myApp', + app_version='1.0.1', + app_environment='dev', + telegram_bot_id='1234567890:AAEwtYwterrqqq4RhXhl637vvvvvv', + telegram_chat_ids={ + 'critical':'-1002233445566', + 'debug':'-2001133445533', + 'warning':'-300223349900' + } +) + +# Invoke from application +logger.debug({'message':'DEBUG_MESSAGE', 'submessage':'TEST'}) +logger.info('INFO_MESSAGE') +logger.warning('WARNING_MESSAGE') +logger.critical('CRITICAL_MESSAGE') +``` + + +# Contributing +Please refer to each project's style and contribution guidelines for submitting patches and additions. In general, we follow the "fork-and-pull" Git workflow. + + 1. **Fork** the repo on GitHub + 2. **Clone** the project to your own machine + 3. **Commit** changes to your own branch + 4. **Push** your work back up to your fork + 5. Submit a **Pull request** so that we can review your changes + +NOTE: Be sure to merge the latest from "upstream" before making a pull request! + + +# License +Apache 2.0 \ No newline at end of file diff --git a/python_app_logger/__init__.py b/python_app_logger/__init__.py new file mode 100644 index 0000000..b593612 --- /dev/null +++ b/python_app_logger/__init__.py @@ -0,0 +1 @@ +from .main import get_logger diff --git a/python_app_logger/main.py b/python_app_logger/main.py new file mode 100644 index 0000000..a544b63 --- /dev/null +++ b/python_app_logger/main.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + +import json +import logging +from copy import copy +from telegram_handler.handlers import TelegramHandler +from telegram_handler.formatters import HtmlFormatter +from telegram_handler.utils import escape_html + + +DEFAULT_LOGLEVEL = 'DEBUG' +DEFAULT_LOGGER_NAME = 'main' + + +class EMOJI: + ''' + Note: + More details and examples at https://www.webfx.com/tools/emoji-cheat-sheet/ + ''' + + WHITE_CIRCLE = '⚪' + GREEN_CIRCLE = '🟢' + BLUE_CIRCLE = '🔵' + RED_CIRCLE = '🔴' + ORANGE_CIRCLE = '🟠' + + +class CustomHtmlFormatter(HtmlFormatter): + ''' + Custom HTML formatter for the Telegram. + ''' + + def format(self, record): + super(HtmlFormatter, self).format(record) + + if record.funcName: + record.funcName = escape_html(str(record.funcName)) + if record.name: + record.name = escape_html(str(record.name)) + if record.msg: + record.msg = escape_html(record.getMessage()) + if self.use_emoji: + if record.levelno == logging.DEBUG: + record.levelname = EMOJI.WHITE_CIRCLE + ' ' + record.levelname + elif record.levelno == logging.INFO: + record.levelname = EMOJI.GREEN_CIRCLE + ' ' + record.levelname + elif record.levelno == logging.WARNING: + record.levelname = EMOJI.ORANGE_CIRCLE + ' ' + record.levelname + else: + record.levelname = EMOJI.RED_CIRCLE + ' ' + record.levelname + + if hasattr(self, '_style'): + return self._style.format(record) + else: + # py2.7 branch + return self._fmt % record.__dict__ + + +class CustomJSONOpensearchFormatter(logging.Formatter): + ''' + Custom JSON formatter to be compatible with Opensearch index format. + + Note: + This formatter need to follow custom JSON format for the JSON nested 'message' field, to be always dict type. + Need for the Opensearch/Elasticsearch correct indexing. + ''' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def format(self, record): + record = copy(record) + if isinstance(record.msg, dict): + record.msg = json.dumps(record.msg) + return super().format(record) + if isinstance(record.msg, str): + log = { + 'message': record.msg + } + record.msg = json.dumps(log) + return super().format(record) + + +class SeverityFilter(object): + + def __init__(self, level): + self.__level = level + + def filter(self, logRecord): + return logRecord.levelno <= self.__level + + +def get_logger( + app_name, + app_version, + app_environment, + loglevel=DEFAULT_LOGLEVEL, + logger_name=DEFAULT_LOGGER_NAME, + telegram_bot_id=None, + telegram_chat_ids={} + ): + ''' + Args: + app_name (str): application name. + app_version (str): application version. + app_environment (str): application environment. + loglevel (str): loglevel (severity). + logger_name (str): logger name. + telegram_bot_id (str): Telegram bot id. + telegram_chat_ids (dict): dict with severity - Telegram chat id mapping. Example: {'debug': '1234567890', 'info': '22334455', 'critical': '9988776655'}. + ''' + + logger = logging.getLogger(logger_name) + + # Stream JSON + handler_json = logging.StreamHandler() + formatter = CustomJSONOpensearchFormatter('{"app": {"name": "%(app_name)s", "localtime": "%(asctime)s", "environment": "%(app_environment)s", "severity": "%(levelname)s", "message": %(message)s, "version": "%(app_version)s", "logger": "%(name)s", "source": "%(pathname)s:%(funcName)s(%(lineno)d)", "source_pathname": "%(pathname)s", "source_funcname": "%(funcName)s", "source_lineno": "%(lineno)d"}}') + handler_json.setFormatter(formatter) + logger.setLevel(loglevel) + logger.addHandler(handler_json) + + # Telegram + telegram_formatter = CustomHtmlFormatter( + use_emoji=True, + fmt = '%(app_name)s (%(app_version)s) %(levelname)s\n\nMessage: %(message)s\nEnvironment: %(app_environment)s\nSource: %(pathname)s:%(funcName)s(%(lineno)d)\nDatetime: %(asctime)s\nLogger: %(name)s\n' + ) + if telegram_bot_id and len(telegram_chat_ids) > 0: + if telegram_chat_ids.get('critical'): + handler_telegram_critical = TelegramHandler(level=logging.CRITICAL, token=telegram_bot_id, chat_id=telegram_chat_ids['critical'], message_thread_id='0') + handler_telegram_critical.setLevel('CRITICAL') + handler_telegram_critical.addFilter(SeverityFilter(logging.CRITICAL)) + handler_telegram_critical.setFormatter(telegram_formatter) + logger.addHandler(handler_telegram_critical) + if telegram_chat_ids.get('warning'): + handler_telegram_warning = TelegramHandler(level=logging.WARNING, token=telegram_bot_id, chat_id=telegram_chat_ids['warning'], message_thread_id='0') + handler_telegram_warning.setLevel('WARNING') + handler_telegram_warning.addFilter(SeverityFilter(logging.WARNING)) + handler_telegram_warning.setFormatter(telegram_formatter) + logger.addHandler(handler_telegram_warning) + if telegram_chat_ids.get('info'): + handler_telegram_info = TelegramHandler(level=logging.INFO, token=telegram_bot_id, chat_id=telegram_chat_ids['info'], message_thread_id='0') + handler_telegram_info.setLevel('INFO') + handler_telegram_info.addFilter(SeverityFilter(logging.INFO)) + handler_telegram_info.setFormatter(telegram_formatter) + logger.addHandler(handler_telegram_info) + if telegram_chat_ids.get('debug'): + handler_telegram_debug = TelegramHandler(level=logging.DEBUG, token=telegram_bot_id, chat_id=telegram_chat_ids['debug'], message_thread_id='0') + handler_telegram_debug.setLevel('DEBUG') + handler_telegram_debug.addFilter(SeverityFilter(logging.DEBUG)) + handler_telegram_debug.setFormatter(telegram_formatter) + logger.addHandler(handler_telegram_debug) + + # Extend formatter with additional fields + logger = logging.LoggerAdapter( + logger, + { + "app_name": app_name, + "app_version": app_version, + "app_environment": app_environment + } + ) + + return logger diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..501cfaa --- /dev/null +++ b/setup.py @@ -0,0 +1,41 @@ +from distutils.core import setup + +VERSION = '1.0.0' +DESCRIPTION = 'A custom python applications logging handler. Use custom JSON format and sends logs via Telegram Bot Api.' +LONG_DESCRIPTION = open('README.md').read() + '\n\n' + open('CHANGELOG.md').read() + + +setup( + name='python-app-logger', + version=VERSION, + description=DESCRIPTION, + long_description=LONG_DESCRIPTION, + long_description_content_type = "text/markdown", + keywords=['telegram', 'logging'], + packages=['python_app_logger'], + url='https://github.com/vfabi/python-app-logger', + download_url='https://github.com/vfabi/python-app-logger/archive/v%s.zip' % VERSION, + setup_requires=['wheel'], + install_requires=[ + 'wheel', + 'python-telegram-handler @ git+https://github.com/vfabi/python-telegram-handler.git@1.0.0' + ], + dependency_links=[ + "git+https://github.com/vfabi/python-telegram-handler.git@1.0.0" + ], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache License', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.8', + 'Topic :: Software Development :: Debuggers', + 'Topic :: System :: Logging' + ], + license='Apache License', + author='vfabi', + author_email='vaad.fabi@gmail.com' +)