aboutsummaryrefslogtreecommitdiffstats
path: root/osdf/logging/osdf_logging.py
blob: 7ebaa99d15390fa93ee20c999be42c3605417467 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# -------------------------------------------------------------------------
#   Copyright (c) 2015-2017 AT&T Intellectual Property
#
#   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.
#
# -------------------------------------------------------------------------
#
import logging
from logging import config
import os
import traceback

import yaml

from osdf.logging import monkey
from osdf.utils.programming_utils import MetaSingleton

BASE_DIR = os.path.dirname(__file__)
LOGGING_FILE = os.path.join(BASE_DIR, '..', '..', 'config', 'log.yml')


def format_exception(err, prefix=None):
    """Format operation for use with ecomp logging

    :param err: exception object
    :param prefix: prefix string message
    :return: formatted exception (via repr(traceback.format_tb(err.__traceback__))
    """
    exception_lines = traceback.format_exception(err.__class__, err, err.__traceback__)
    exception_desc = "".join(exception_lines)
    return exception_desc if not prefix else prefix + ": " + exception_desc


def create_log_dirs():
    with open(LOGGING_FILE, 'r') as fid:
        yaml_config = yaml.full_load(fid)
    for key in yaml_config['handlers']:
        a = yaml_config['handlers'][key]
        if a.get('filename'):
            os.makedirs(os.path.dirname(a['filename']), exist_ok=True)


class OOFOSDFLogMessageHelper(metaclass=MetaSingleton):
    """Provides loggers as a singleton (otherwise, we end up with duplicate messages).

    Provides error_log, metric_log, audit_log, and debug_log (in that order)

    Additionally can provide specific log handlers too
    """
    log_handlers = None
    default_levels = ["error", "metrics", "audit", "debug"]

    def get_handlers(self, levels=None):
        """Return ONAP-compliant log handlers for different levels. Each "level" ends up in a different log file

        with a prefix of that level.

        For example: error_log, metrics_log, audit_log, debug_log in that order

        :param levels: None or list of levels subset of self.default_levels (["error", "metrics", "audit", "debug"])

        :return: list of log_handlers in the order of levels requested.
              if levels is None: we return handlers for self.default_levels
              if levels is ["error", "audit"], we return log handlers for that.
        """
        create_log_dirs()
        monkey.patch_all()
        config.yamlConfig(filepath=LOGGING_FILE, watchDog=False)
        wanted_levels = self.default_levels if levels is None else levels
        return [logging.getLogger(x) for x in wanted_levels]


class OOFOSDFLogMessageFormatter(object):

    @staticmethod
    def accepted_valid_request(req_id, request):
        """valid request message formatter

        """
        return "Accepted valid request for ID: {} for endpoint: {}".format(
            req_id, request.url)

    @staticmethod
    def invalid_request(req_id, err):
        """invalid request message formatter

        """
        return "Invalid request for request ID: {}; cause: {}".format(
            req_id, format_exception(err))

    @staticmethod
    def invalid_response(req_id, err):
        """invalid response message formatter

        """
        return "Invalid response for request ID: {}; cause: {}".format(
            req_id, format_exception(err))

    @staticmethod
    def malformed_request(request, err):
        """Malformed request message formatter

        """
        return "Malformed request for URL {}, from {}; cause: {}".format(
            request.url, request.remote_address, format_exception(err))

    @staticmethod
    def malformed_response(response, client, err):
        """Malformed response message formatter

        """
        return "Malformed response {} for client {}; cause: {}".format(
            response, client, format_exception(err))

    @staticmethod
    def need_policies(req_id):
        """Policies needed message formatter

        """
        return "Policies required but found no policies for request ID: {}".format(req_id)

    @staticmethod
    def policy_service_error(url, req_id, err):
        """policy service error message formatter

        """
        return "Unable to call policy for {} for request ID: {}; cause: {}".format(
            url, req_id, format_exception(err))

    @staticmethod
    def requesting_url(url, req_id):
        """Message formatter for requesting url

        """
        return "Making a call to URL {} for request ID: {}".format(url, req_id)

    @staticmethod
    def requesting(service_name, req_id):
        """Message formatter for requesting a service

        """
        return "Making a call to service {} for request ID: {}".format(service_name, req_id)

    @staticmethod
    def error_requesting(service_name, req_id, err):
        """Message formatter on error requesting a service

        """
        return "Error while requesting service {} for request ID: {}; cause: {}".format(
            service_name, req_id, format_exception(err))

    @staticmethod
    def calling_back(req_id, callback_url):
        """Message formatter when a callback url is invoked

        """
        return "Posting result to callback URL for request ID: {}; callback URL={}".format(
            req_id, callback_url)

    @staticmethod
    def calling_back_with_body(req_id, callback_url, body):
        """Message formatter when a callback url with body is invoked

        """
        return "Posting result to callback URL for request ID: {}; callback URL={} body={}".format(
            req_id, callback_url, body)

    @staticmethod
    def error_calling_back(req_id, callback_url, err):
        """Message formatter on an error while posting result to callback url

        """
        return "Error while posting result to callback URL {} for request ID: {}; cause: {}".format(
            req_id, callback_url, format_exception(err))

    @staticmethod
    def received_request(url, remote_addr, json_body):
        """Message when a call is received

        """
        return "Received a call to {} from {} {}".format(url, remote_addr, json_body)

    @staticmethod
    def new_worker_thread(req_id, extra_msg=""):
        """Message on invoking a new worker thread

        """
        res = "Initiating new worker thread for request ID: {}".format(req_id)
        return res + extra_msg

    @staticmethod
    def inside_worker_thread(req_id, extra_msg=""):
        """Message when inside a worker thread

        """
        res = "Inside worker thread for request ID: {}".format(req_id)
        return res + extra_msg

    @staticmethod
    def processing(req_id, desc):
        """Processing a request

        """
        return "Processing request ID: {} -- {}".format(req_id, desc)

    @staticmethod
    def processed(req_id, desc):
        """Processed the request

        """
        return "Processed request ID: {} -- {}".format(req_id, desc)

    @staticmethod
    def error_while_processing(req_id, desc, err):
        """Error while processing the request

        """
        return "Error while processing request ID: {} -- {}; cause: {}".format(
            req_id, desc, format_exception(err))

    @staticmethod
    def creating_local_env(req_id):
        """Creating a local environment

        """
        return "Creating local environment request ID: {}".format(
            req_id)

    @staticmethod
    def error_local_env(req_id, desc, err):
        """Error creating a local env

        """
        return "Error while creating local environment for request ID: {} -- {}; cause: {}".format(
            req_id, desc, err.__traceback__)

    @staticmethod
    def inside_new_thread(req_id, extra_msg=""):
        """Inside a new thread

        """
        res = "Spinning up a new thread for request ID: {}".format(req_id)
        return res + " " + extra_msg

    @staticmethod
    def error_response_posting(req_id, desc, err):
        """Error while posting a response

        """
        return "Error while posting a response for a request ID: {} -- {}; cause: {}".format(
            req_id, desc, err.__traceback__)

    @staticmethod
    def received_http_response(resp):
        """Received a http response

        """
        return "Received response [code: {}, headers: {}, data: {}]".format(
            resp.status_code, resp.headers, resp.__dict__)

    @staticmethod
    def sending_response(req_id, desc):
        """sending a response

        """
        return "Response is sent for request ID: {} -- {}".format(
            req_id, desc)

    @staticmethod
    def listening_response(req_id, desc):
        """Resposne is sent for a request ID

        """
        return "Response is sent for request ID: {} -- {}".format(
            req_id, desc)

    @staticmethod
    def items_received(item_num, item_type, desc="Received"):
        """Items received

        """
        return "{} {} {}".format(desc, item_num, item_type)

    @staticmethod
    def items_sent(item_num, item_type, desc="Published"):
        """items published

        """
        return "{} {} {}".format(desc, item_num, item_type)


MH = OOFOSDFLogMessageFormatter

error_log, metrics_log, audit_log, debug_log = OOFOSDFLogMessageHelper().get_handlers()


def warn_audit_error(msg):
    """Log the message to error_log.warn and audit_log.warn

    """
    log_message_multi(msg, audit_log.warn, error_log.warn)


def log_message_multi(msg, *logger_methods):
    """Log the msg to multiple loggers

    :param msg: message to log
    :param logger_methods: e.g. error_log.warn, audit_log.warn, etc.
    """
    for log_method in logger_methods:
        log_method(msg)