[IMP] runbot: add log metadata

This commit is contained in:
Xavier-Do 2025-03-03 10:56:05 +01:00
parent 1ed9278d6e
commit d1cd7f3684
4 changed files with 24 additions and 10 deletions

View File

@ -409,27 +409,26 @@ class BuildError(models.Model):
build_error_contents = self.env['runbot.build.error.content'] build_error_contents = self.env['runbot.build.error.content']
# add build ids to already detected errors # add build ids to already detected errors
existing_errors_contents = self.env['runbot.build.error.content'].search([('fingerprint', 'in', list(hash_dict.keys())), ('error_id.active', '=', True)]) existing_errors_contents = self.env['runbot.build.error.content'].search([('fingerprint', 'in', list(hash_dict.keys())), ('error_id.active', '=', True)])
existing_fingerprints = existing_errors_contents.mapped('fingerprint') existing_fingerprints = {error.fingerprint: error for error in existing_errors_contents}
build_error_contents |= existing_errors_contents build_error_contents |= existing_errors_contents
# for build_error_content in existing_errors_contents:
# logs = hash_dict[build_error_content.fingerprint]
# # update filepath if it changed. This is optionnal and mainly there in case we adapt the OdooRunner log
# if logs[0].path != build_error_content.file_path:
# build_error_content.file_path = logs[0].path
# build_error_content.function = logs[0].func
# create an error for the remaining entries # create an error for the remaining entries
for fingerprint, logs in hash_dict.items(): for fingerprint, logs in hash_dict.items():
if fingerprint in existing_fingerprints: if fingerprint in existing_fingerprints:
# metadata update, keep this for a while
error = existing_fingerprints[fingerprint]
if not error.metadata and logs[0].metadata:
error.metadata = logs[0].metadata
continue continue
new_build_error_content = self.env['runbot.build.error.content'].create({ new_build_error_content = self.env['runbot.build.error.content'].create({
'content': logs[0].message, 'content': logs[0].message,
'module_name': logs[0].name.removeprefix('odoo.').removeprefix('addons.'), 'module_name': logs[0].name.removeprefix('odoo.').removeprefix('addons.'),
'file_path': logs[0].path, 'file_path': logs[0].path,
'function': logs[0].func, 'function': logs[0].func,
'metadata': logs[0].metadata,
}) })
build_error_contents |= new_build_error_content build_error_contents |= new_build_error_content
existing_fingerprints.append(fingerprint) existing_fingerprints[fingerprint] = new_build_error_content
for build_error_content in build_error_contents: for build_error_content in build_error_contents:
logs = hash_dict[build_error_content.fingerprint] logs = hash_dict[build_error_content.fingerprint]
@ -474,6 +473,7 @@ class BuildErrorContent(models.Model):
error_display_id = fields.Integer(compute='_compute_error_display_id', string="Error id") error_display_id = fields.Integer(compute='_compute_error_display_id', string="Error id")
content = fields.Text('Error message', required=True) content = fields.Text('Error message', required=True)
cleaned_content = fields.Text('Cleaned error message') cleaned_content = fields.Text('Cleaned error message')
metadata = JsonDictField('Metadata')
summary = fields.Char('Content summary', compute='_compute_summary', store=False) summary = fields.Char('Content summary', compute='_compute_summary', store=False)
module_name = fields.Char('Module name') # name in ir_logging module_name = fields.Char('Module name') # name in ir_logging
file_path = fields.Char('File Path') # path in ir logging file_path = fields.Char('File Path') # path in ir logging

View File

@ -94,10 +94,21 @@ class Host(models.Model):
path character varying NOT NULL, path character varying NOT NULL,
line character varying NOT NULL, line character varying NOT NULL,
type character varying NOT NULL, type character varying NOT NULL,
metadata jsonb,
message text NOT NULL); message text NOT NULL);
""") """)
except Exception as e: except Exception as e:
_logger.exception('Failed to create local logs database: %s', e) _logger.exception('Failed to create local logs database: %s', e)
else:
# TODO cleanup remove in 20.0
with local_pg_cursor(logs_db_name) as local_cr:
local_cr.execute("""SELECT 1
FROM information_schema.columns
WHERE table_name='ir_logging' and column_name='metadata'""")
if not local_cr.fetchone():
_logger.info('Adding metadata column to ir_logging table')
local_cr.execute("""ALTER TABLE ir_logging ADD COLUMN metadata jsonb""")
def _bootstrap_db_template(self): def _bootstrap_db_template(self):
""" boostrap template database if needed """ """ boostrap template database if needed """
@ -237,7 +248,7 @@ class Host(models.Model):
query = f""" query = f"""
SELECT * SELECT *
FROM ( FROM (
SELECT id, create_date, name, level, dbname, func, path, line, type, message, split_part(dbname, '-', 1) as build_id SELECT id, create_date, name, level, dbname, func, path, line, type, message, split_part(dbname, '-', 1) as build_id, metadata
FROM ir_logging FROM ir_logging
) )
AS ir_logs AS ir_logs

View File

@ -5,6 +5,7 @@ import logging
from collections import defaultdict from collections import defaultdict
from ..common import pseudo_markdown from ..common import pseudo_markdown
from ..fields import JsonDictField
from odoo import models, fields, tools, api from odoo import models, fields, tools, api
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools import html_escape from odoo.tools import html_escape
@ -24,6 +25,7 @@ class IrLogging(models.Model):
type = fields.Selection(selection_add=TYPES, string='Type', required=True, index=True, ondelete={t[0]: 'cascade' for t in TYPES}) type = fields.Selection(selection_add=TYPES, string='Type', required=True, index=True, ondelete={t[0]: 'cascade' for t in TYPES})
error_content_id = fields.Many2one('runbot.build.error.content', compute='_compute_known_error') # remember to never store this field error_content_id = fields.Many2one('runbot.build.error.content', compute='_compute_known_error') # remember to never store this field
dbname = fields.Char(string='Database Name', index=False) dbname = fields.Char(string='Database Name', index=False)
metadata = JsonDictField('Metadata')
@api.model_create_multi @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):

View File

@ -221,6 +221,7 @@
</group> </group>
<group name="build_error_group" string="Base info" col="2"> <group name="build_error_group" string="Base info" col="2">
<field name="content" readonly="1"/> <field name="content" readonly="1"/>
<field name="metadata" readonly="1"/>
<field name="module_name" readonly="1"/> <field name="module_name" readonly="1"/>
<field name="function" readonly="1"/> <field name="function" readonly="1"/>
<field name="file_path" readonly="1"/> <field name="file_path" readonly="1"/>