التسجيل الهرمي للتطبيق لقاعدة البيانات

تحية للجميع!


في المقالة ، أود أن أتحدث عن أحد الأساليب في تسجيل التطبيق ، والذي ساعدني وزملائي بشكل كبير في تصحيح الأخطاء وإصلاحها ، وحل مشاكل الأداء. تمت كتابة الكثير من المقالات الجيدة حول الحاجة إلى التسجيل ، بما في ذلك التسجيل في حبري ؛ وبالتالي ، ليس هناك الكثير من المعقول للتكرار هنا. بدأت مسيرتي في مجال تكنولوجيا المعلومات مع Oracle ودرست قاعدة البيانات هذه من كتب الخبير الرئيسي - Tom Kite. أتذكر عبارته عن تسجيل الدخول من كتاب "أوراكل الفعالة حسب التصميم":

الأجهزة ليست علوية. النفقات العامة هي شيء يمكنك إزالته دون فقدان الكثير من الفوائد. إزالة (أو عدم وجود) الأجهزة يسلب وظائف كبيرة. لن تحتاج إلى القيام بذلك إذا لم تنكسر أنظمةك أبدًا ، ولا تحتاج أبدًا إلى التشخيص ، ولا تعاني أبدًا من مشكلات في الأداء. إذا كان هذا صحيحًا ، فأنت لست بحاجة إلى إعداد النظام الخاص بك (وأرسل لي عنوان بريدك الإلكتروني ، لأن لدي عرض عمل لك).


مع العمل على مشاريع أوراكل ، بدأ كل شيء.


تسجيل هرمي في أوراكل


منذ بعض الوقت كنت أعمل على مشروع لتطوير مستودع بيانات كبير على نظام Oracle الأساسي. تم تنفيذ منطق تحميل وفحص وإثراء البيانات داخل قاعدة البيانات في PL / SQL. فيما يتعلق بالتسجيل ، تم اقتراح الفكرة التالية. إذا نظرت عالميًا إلى أي تطبيق ، فهي عبارة عن شجرة استدعاءات للوظائف / الإجراءات (الأساليب). في هذه الحالة ، يمكن لطريقة بداخلها استدعاء عدة طرق فرعية ، ولكن نداءها يحدث بالضبط من طريقة رئيسية واحدة. وبالتالي ، نحصل على تسلسل هرمي للوالد / الطفل بين الأساليب (تسمى / تسمى) ، والذي يتم تصميمه بشكل طبيعي في قاعدة البيانات باستخدام حقول id ، ووالد_الأسماء ، حيث يمثل الوالدان المفتاح الخارجي في حقل المعرّف. هذه هي نفس العلاقة تمامًا كما في المثال الكلاسيكي مع الموظفين ومديريهم (يمكن أن يكون للموظف مدير واحد فقط ، ويمكن أن يكون للمدير عدة موظفين). يمكن وصف مخطط التسجيل على النحو التالي:


create table log_instances (
  start_log_id number(16) not null
  , log_instance_name varchar2(100) not null
  , start_ts timestamp(6) not null
  , end_ts timestamp(6)
  , status varchar2(1) not null
  , log_date date not null,
  , constraint log_instances_pk primary key(start_log_id))
/

create index log_instances_name_idx on log_instances(log_instance_name)
/

create table log_table (
  action_name varchar2(64) not null,
  log_id  NUMBER(16) not null,
  parent_log_id NUMBER(16),
  start_ts timestamp(6) not null,
  end_ts timestamp(6),
  status varchar2(1) not null,
  comments varchar2(4000),
  exception_message varchar2(4000),
  large_text CLOB,
  log_date date not null,
  constraint log_table_status_chck check (status in ('C'/*completed*/, 'R'/*running*/, 'F'/*failed*/))
)
partition by range (log_date)
interval(NUMTODSINTERVAL(7,'day'))
(partition p_fst_day_of_week values less than (date '...'))
/

create index log_table_log_id_idx on log_table(log_id) local
/

create index log_table_parent_id_idx on log_table(parent_log_id) local
/

create index log_table_action_name_idx on log_table(action_name) local
/

يحتوي جدول log_instance الأول على معلومات حول بدء كل تطبيق:


  • log_instance_name - الاسم ، على سبيل المثال ، "sample_app_20200501"
  • start_ts / end_ts - بداية ونهاية الوقت من تسجيل ( end_ts هو فارغة إذا كان التطبيق قيد التشغيل حاليا)
  • الحالة - "C" (مكتمل) - تم إنهاء التطبيق دون أخطاء ، "R" (قيد التشغيل) - التطبيق قيد التشغيل ، "F" (فشل) - تم إنهاء التطبيق بسبب خطأ.
  • start_log_id هو معرف الجذر في التسلسل الهرمي للمكالمات.

log_table .


  • action_name
  • start_ts/end_ts — /
  • status — 'C' (completed) — , 'R'(running) — , 'F'(failed) — .
  • exception_message — , error stack trace ,

API ( Oracle API / ). :


  • PROCEDURE start_logging(instance_name) — , , log_instances
  • PROCEDURE open_next_level(action_name, comments, clob_text) — . log_table
  • PROCEDURE close_level_success — 'C'
  • PROCEDURE close_level_fail — 'F'
  • PROCEDURE stop_log_success — 'C'
  • PROCEDURE stop_log_fail — 'F'


( Oracle ):


  • id
  • id, INSERT/UPDATE log_table
  • id log_table ,

pk_util_log . API :
-infoopen_level close_level. - logger.info Java.
-resume_logging (parent_log_id) — . id log_table.



DECLARE
    PROCEDURE b(p_name_in IN VARCHAR2) IS
        v_dummy_cnt PLS_INTEGER;
    BEGIN
        pk_util_log.open_next_level(p_action_name_in => 'In procedure b()',
                        p_comments_in => 'procedure B(), line: ' || $$PLSQL_LINE || chr(13) || chr(10) ||
                                                     'p_name_in: ' || p_name_in);
        dbms_lock.sleep(3);
        pk_util_log.close_level_success;
    EXCEPTION
        WHEN OTHERS THEN
            pk_util_log.close_level_fail;
            RAISE;
    END;

    PROCEDURE a(p_name_in IN VARCHAR2) IS
    BEGIN
        pk_util_log.open_next_level(p_action_name_in => 'In procedure a()',
                            p_comments_in => 'procedure A(), line: ' || $$PLSQL_LINE || chr(13) || chr(10) ||
                                    'p_name_in: ' || p_name_in);
        b('dummy_b');
        dbms_lock.sleep(2);
        pk_util_log.close_level_success;
    EXCEPTION
        WHEN OTHERS THEN
            pk_util_log.close_level_fail;
            RAISE;
    END;

BEGIN
    pk_util_log.start_logging('sample_app_20200501');
    dbms_output.put_line(pk_util_log.get_start_log_id);
    a('dummy_a');
    pk_util_log.stop_log_success;
exception
    when others then
        pk_util_log.stop_log_fail;
        raise;
END;
/

start_log_id:


select start_log_id from log_instances where log_instance_name = 'sample_app_20200501';

start_log_id :


SELECT
    LPAD (' ', 2* (LEVEL- 1)) || l.action_name as action_name,
    l.status,
    l.start_ts,
    l.end_ts,
    l.comments
FROM
    tech_log_table l
START WITH l.log_id = 204 /*start_log_id*/
CONNECT BY
    l.parent_log_id = PRIOR l.log_id
ORDER SIBLINGS BY
    l.log_id ASC;

ACTION_NAMESTATUSSTART_TSEND_TSCOMMENTS
sample_app_20200501C2020-05-01 16:37:46.7531682020-05-01 16:37:51.755380NULL
In procedure a()C2020-05-01 16:37:46.7535542020-05-01 16:37:51.754649procedure A(), line: 19
p_name_in: dummy_a
In procedure b()C2020-05-01 16:37:46.7538692020-05-01 16:37:49.753746procedure B(), line: 6
p_name_in: dummy_b

, a(p_name_in) b(p_name_in).



  1. , , , .
  2. , , . : , .
  3. , , . SQL . email id .

:


  1. DML log_table (log_id). log_id sequence Oracle, . sequence .
  2. log_table , . Oracle , . , .

Oracle , Java. Java API.


Java


, , . PostgreSql Oracle Open Source.


, Connection , Connection Pool ( HikariCP).


. Java thread'. ThreadLocal, InheritableThreadLocal, thread .


API


:


  • public static void startLog(final String logInstanceName)
  • public static void openNextLevel(final String actionName, final String comments)
  • public static void stopLogSuccess()
  • public static void stopLogFail(final Exception exception)
  • public static void closeLevelSuccess()
  • public static void closeLevelFail(final Exception exception)

:


public class A {
    public static void main(String[] args) {
        try {
            LogUtils.startLog("sample_app_20200501");
            A a = new A();
            a.method1("some string");
            LogUtils.stopLogSuccess();
        } catch (Exception e) {
            LogUtils.stopLogFail(e);
            throw new RuntimeException(e);
        }
    }

    private String method1(String s) {
        try {
            LogUtils.openNextLevel("method1", "Arguments: " + s);
            method2(1, 2);
            LogUtils.closeLevelSuccess();
            return s;
        } catch (Exception e) {
            LogUtils.closeLevelFail(e);
            throw new RuntimeException(e);
        }
    }

    private long method2(int i1, int i2) {
        try {
            LogUtils.openNextLevel("method2", "Arguments: " + i1 + ", " + i2);
            LogUtils.closeLevelSuccess();
            return i1 + i2;
        } catch (Exception e) {
            LogUtils.closeLevelFail(e);
            throw new RuntimeException(e);
        }
    }

}

, log_instances id (start_log_id) . PostgreSql RECURSIVE WITH:


WITH RECURSIVE log AS (
    SELECT 1                as level,
           ARRAY [l.log_id] AS path,
           l.log_id,
           l.action_name,
           l.parent_log_id,
           l.start_ts,
           l.end_ts,
           l.status,
           l.comments,
           l.exception_message
    FROM log_table l
    WHERE l.log_id = ...
    UNION ALL
    SELECT l.level + 1 as level,
           path || l1.log_id,
           l1.log_id,
           l1.action_name,
           l1.parent_log_id,
           l1.start_ts,
           l1.end_ts,
           l1.status,
           l1.comments,
           l1.exception_message
    FROM log_table l1
             INNER JOIN log l ON l.log_id = l1.parent_log_id
)
SELECT
       lpad(' ', (l.level - 1) * 2) || l.log_id as log_id,
       l.action_name,
       l.start_ts,
       l.end_ts,
       l.end_ts - l.start_ts as duration,
       l.status,
       l.comments,
       l.exception_message
FROM log l
order by l.path, l.start_ts;

Boilerplate Aspect Oriented Programming


, , , exception :


try {
    LogUtils.openNextLevel(...);
    ...
    LogUtils.closeLevelSuccess();
} catch (Exception e) {
    LogUtils.closeLevelFail(e);
    throw new RuntimeException(e);
}

"" , . code templates, IDE . : Aspect Oriented Programming (AOP). , , AOP — , (cross-cutting concerns). , , . AOP (advice) , (pointcut). pointcut advice . , . (weave) : compile-time, post-compile, load-time. compile-time weaving, AOP AspectJ.


, @LogToDb, , . :


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogToDb {
    boolean suppressLogArgs() default false;
    boolean suppressLogResult() default false;
}

:


@Aspect
public class LogToDbAspect {
    @Pointcut("@annotation(logToDb) && execution(* *.*(..))")
    public void logToDbPointcut(LogToDb logToDb) {
    }

    @Around(value = "logToDbPointcut(logToDb)")
    public Object around(ProceedingJoinPoint pjp, LogToDb logToDb) throws Throwable {
        try {
            LogUtils.openNextLevel(pjp.getSignature().toShortString(),
                    logToDb.suppressLogArgs() ? null : AspectUtils.getArgsString(pjp.getArgs()));
            Object result = pjp.proceed();
            if (!logToDb.suppressLogResult()) {
                LogUtils.addComments("\nResult: " + result.toString());
            }
            LogUtils.closeLevelSuccess();
            return result;
        } catch (Exception e) {
            LogUtils.closeLevelFail(e);
            throw new RuntimeException(e);
        }
    }
}

boilerplate :


private String method1(String s) {
    try {
        LogUtils.openNextLevel("method1", "Arguments: " + s);
        LogUtils.addComments("\nResult: " + s);
        LogUtils.closeLevelSuccess();
        return s;
    } catch (Exception e) {
        LogUtils.closeLevelFail(e);
        throw new RuntimeException(e);
    }
}

:


@LogToDb
private String method1(String s) {
    return s;
}

:


  1. compile-time weaving aspectjc.
  2. aspectjc , . aspectjc Lombok. :

[WARNING] You aren't using a compiler supported by lombok, so lombok will not work and has been disabled.
Your processor is: org.aspectj.org.eclipse.jdt.internal.compiler.apt.dispatch.BatchProcessingEnvImpl
Lombok supports: sun/apple javac 1.6, ECJ

lombok-maven-plugin, aspectjc lombok.



:


  1. :
    • exception
    • parent/child (/)
  2. :
    • SQL ,
    • ,
  3. :
    • boilerplate
  4. Java :
    • . DevOps .
    • boilerplate AOP, ( , runtime).
    • , . Connection pool, , , — . . DML .


GitHub:


تنفيذ أوراكل:


  1. https://github.com/nikita-mospan/plsql-liquibase-utplsql/blob/master/src/main/resources/oracle/tech_user/packages/pk_util_log.pks
  2. https://github.com/nikita-mospan/plsql-liquibase-utplsql/blob/master/src/main/resources/oracle/tech_user/packages/pk_util_log.pkb

تنفيذ جافا:


  1. https://github.com/nikita-mospan/log-to-db

All Articles