e457f18b11b4837fd344aa7a22abb0cba2972542
[optf/osdf.git] / osdf / logging / osdf_logging.py
1 # -------------------------------------------------------------------------
2 #   Copyright (c) 2015-2017 AT&T Intellectual Property
3 #
4 #   Licensed under the Apache License, Version 2.0 (the "License");
5 #   you may not use this file except in compliance with the License.
6 #   You may obtain a copy of the License at
7 #
8 #       http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #   Unless required by applicable law or agreed to in writing, software
11 #   distributed under the License is distributed on an "AS IS" BASIS,
12 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #   See the License for the specific language governing permissions and
14 #   limitations under the License.
15 #
16 # -------------------------------------------------------------------------
17 #
18
19 import logging
20 import os
21 import traceback
22 from logging import config
23
24 import yaml
25 from onaplogging import monkey
26
27 from osdf.utils.programming_utils import MetaSingleton
28
29 BASE_DIR = os.path.dirname(__file__)
30 LOGGING_FILE = os.path.join(BASE_DIR, '..', '..', 'config', 'log.yml')
31
32
33 def format_exception(err, prefix=None):
34     """Format operation for use with ecomp logging
35     :param err: exception object
36     :param prefix: prefix string message
37     :return: formatted exception (via repr(traceback.format_tb(err.__traceback__))
38     """
39     exception_lines = traceback.format_exception(err.__class__, err, err.__traceback__)
40     exception_desc = "".join(exception_lines)
41     return exception_desc if not prefix else prefix + ": " + exception_desc
42
43
44 def create_log_dirs():
45     with open(LOGGING_FILE, 'r') as fid:
46         yaml_config = yaml.full_load(fid)
47     for key in yaml_config['handlers']:
48         a = yaml_config['handlers'][key]
49         if a.get('filename'):
50             os.makedirs(os.path.dirname(a['filename']), exist_ok=True)
51
52
53 class OOF_OSDFLogMessageHelper(metaclass=MetaSingleton):
54     """Provides loggers as a singleton (otherwise, we end up with duplicate messages).
55     Provides error_log, metric_log, audit_log, and debug_log (in that order)
56     Additionally can provide specific log handlers too
57     """
58     log_handlers = None
59     default_levels = ["error", "metrics", "audit", "debug"]
60
61     def get_handlers(self, levels=None):
62         """Return ONAP-compliant log handlers for different levels. Each "level" ends up in a different log file
63         with a prefix of that level.
64
65         For example: error_log, metrics_log, audit_log, debug_log in that order
66         :param levels: None or list of levels subset of self.default_levels (["error", "metrics", "audit", "debug"])
67         :param log_version: Currently only pre_onap is supported
68         :param config_file: Logging configuration file for ONAP compliant logging
69         :param service_name: Name of the service
70         :return: list of log_handlers in the order of levels requested.
71               if levels is None: we return handlers for self.default_levels
72               if levels is ["error", "audit"], we return log handlers for that.
73         """
74         create_log_dirs()
75         monkey.patch_all()
76         config.yamlConfig(filepath=LOGGING_FILE, watchDog=False)
77         wanted_levels = self.default_levels if levels is None else levels
78         return [logging.getLogger(x) for x in wanted_levels]
79
80
81 class OOF_OSDFLogMessageFormatter(object):
82
83     @staticmethod
84     def accepted_valid_request(req_id, request):
85         return "Accepted valid request for ID: {} for endpoint: {}".format(
86             req_id, request.url)
87
88     @staticmethod
89     def invalid_request(req_id, err):
90         return "Invalid request for request ID: {}; cause: {}".format(
91             req_id, format_exception(err))
92
93     @staticmethod
94     def invalid_response(req_id, err):
95         return "Invalid response for request ID: {}; cause: {}".format(
96             req_id, format_exception(err))
97
98     @staticmethod
99     def malformed_request(request, err):
100         return "Malformed request for URL {}, from {}; cause: {}".format(
101             request.url, request.remote_address, format_exception(err))
102
103     @staticmethod
104     def malformed_response(response, client, err):
105         return "Malformed response {} for client {}; cause: {}".format(
106             response, client, format_exception(err))
107
108     @staticmethod
109     def need_policies(req_id):
110         return "Policies required but found no policies for request ID: {}".format(req_id)
111
112     @staticmethod
113     def policy_service_error(url, req_id, err):
114         return "Unable to call policy for {} for request ID: {}; cause: {}".format(
115             url, req_id, format_exception(err))
116
117     @staticmethod
118     def requesting_url(url, req_id):
119         return "Making a call to URL {} for request ID: {}".format(url, req_id)
120
121     @staticmethod
122     def requesting(service_name, req_id):
123         return "Making a call to service {} for request ID: {}".format(service_name, req_id)
124
125     @staticmethod
126     def error_requesting(service_name, req_id, err):
127         return "Error while requesting service {} for request ID: {}; cause: {}".format(
128             service_name, req_id, format_exception(err))
129
130     @staticmethod
131     def calling_back(req_id, callback_url):
132         return "Posting result to callback URL for request ID: {}; callback URL={}".format(
133             req_id, callback_url)
134
135     @staticmethod
136     def calling_back_with_body(req_id, callback_url, body):
137         return "Posting result to callback URL for request ID: {}; callback URL={} body={}".format(
138             req_id, callback_url, body)
139
140     @staticmethod
141     def error_calling_back(req_id, callback_url, err):
142         return "Error while posting result to callback URL {} for request ID: {}; cause: {}".format(
143             req_id, callback_url, format_exception(err))
144
145     @staticmethod
146     def received_request(url, remote_addr, json_body):
147         return "Received a call to {} from {} {}".format(url, remote_addr, json_body)
148
149     @staticmethod
150     def new_worker_thread(req_id, extra_msg=""):
151         res = "Initiating new worker thread for request ID: {}".format(req_id)
152         return res + extra_msg
153
154     @staticmethod
155     def inside_worker_thread(req_id, extra_msg=""):
156         res = "Inside worker thread for request ID: {}".format(req_id)
157         return res + extra_msg
158
159     @staticmethod
160     def processing(req_id, desc):
161         return "Processing request ID: {} -- {}".format(req_id, desc)
162
163     @staticmethod
164     def processed(req_id, desc):
165         return "Processed request ID: {} -- {}".format(req_id, desc)
166
167     @staticmethod
168     def error_while_processing(req_id, desc, err):
169         return "Error while processing request ID: {} -- {}; cause: {}".format(
170             req_id, desc, format_exception(err))
171
172     @staticmethod
173     def creating_local_env(req_id):
174         return "Creating local environment request ID: {}".format(
175             req_id)
176
177     @staticmethod
178     def error_local_env(req_id, desc, err):
179         return "Error while creating local environment for request ID: {} -- {}; cause: {}".format(
180             req_id, desc, err.__traceback__)
181
182     @staticmethod
183     def inside_new_thread(req_id, extra_msg=""):
184         res = "Spinning up a new thread for request ID: {}".format(req_id)
185         return res + " " + extra_msg
186
187     @staticmethod
188     def error_response_posting(req_id, desc, err):
189         return "Error while posting a response for a request ID: {} -- {}; cause: {}".format(
190             req_id, desc, err.__traceback__)
191
192     @staticmethod
193     def received_http_response(resp):
194         return "Received response [code: {}, headers: {}, data: {}]".format(
195             resp.status_code, resp.headers, resp.__dict__)
196
197     @staticmethod
198     def sending_response(req_id, desc):
199         return "Response is sent for request ID: {} -- {}".format(
200             req_id, desc)
201
202     @staticmethod
203     def listening_response(req_id, desc):
204         return "Response is sent for request ID: {} -- {}".format(
205             req_id, desc)
206
207     @staticmethod
208     def items_received(item_num, item_type, desc="Received"):
209         return "{} {} {}".format(desc, item_num, item_type)
210
211     @staticmethod
212     def items_sent(item_num, item_type, desc="Published"):
213         return "{} {} {}".format(desc, item_num, item_type)
214
215
216 MH = OOF_OSDFLogMessageFormatter
217
218 error_log, metrics_log, audit_log, debug_log = OOF_OSDFLogMessageHelper().get_handlers()
219
220
221 def warn_audit_error(msg):
222     """Log the message to error_log.warn and audit_log.warn"""
223     log_message_multi(msg, audit_log.warn, error_log.warn)
224
225
226 def log_message_multi(msg, *logger_methods):
227     """Log the msg to multiple loggers
228     :param msg: message to log
229     :param logger_methods: e.g. error_log.warn, audit_log.warn, etc.
230     """
231     for log_method in logger_methods:
232         log_method(msg)