Pencatatan hierarki aplikasi ke basis data

Halo semuanya!


Dalam artikel ini, saya ingin berbicara tentang salah satu pendekatan untuk pencatatan aplikasi, yang sangat membantu saya dan rekan saya dalam debugging, pemecahan masalah, dan menganalisis masalah kinerja. Banyak artikel bagus ditulis tentang perlunya penebangan, termasuk di Habré, oleh karena itu, tidak ada alasan untuk mengulang di sini. Saya memulai karir TI saya dengan Oracle dan mempelajari basis data ini dari buku-buku ahli utama - Tom Kite. Saya ingat ungkapannya tentang masuk dari buku "Oracle Efektif oleh Desain":

Instrumentasi bukan overhead. Overhead adalah sesuatu yang dapat Anda hapus tanpa kehilangan banyak manfaat. Menghapus (atau tidak memiliki) instrumentasi menghilangkan banyak fungsi. Anda tidak perlu melakukan ini jika sistem Anda tidak pernah rusak, tidak pernah memerlukan diagnostik, dan tidak pernah menderita masalah kinerja. Jika itu benar, Anda tidak perlu menginstruksikan sistem Anda (dan kirimkan saya alamat email Anda, karena saya memiliki tawaran pekerjaan untuk Anda).


Dengan bekerja pada proyek-proyek Oracle, semuanya dimulai.


Hierarchical Logging di Oracle


Beberapa waktu lalu saya sedang mengerjakan proyek untuk mengembangkan Gudang Data besar pada platform Oracle. Logika memuat, memeriksa, memperkaya data diimplementasikan di dalam database dalam PL / SQL. Berkenaan dengan penebangan, ide berikut diusulkan. Jika Anda melihat secara global pada aplikasi apa pun, maka itu adalah pohon panggilan ke fungsi / prosedur (metode). Pada saat yang sama, suatu metode di dalam dirinya sendiri dapat memanggil beberapa metode anak, tetapi panggilannya terjadi persis dari satu metode induk. Dengan demikian, kita mendapatkan hierarki induk / anak antara metode (disebut / dipanggil), yang secara alami dimodelkan dalam database menggunakan bidang id , parent_id , di mana parent_id adalah kunci asing di bidang id. Ini persis hubungan yang sama seperti dalam contoh klasik dengan karyawan dan manajer mereka (karyawan hanya dapat memiliki satu manajer, manajer dapat memiliki beberapa karyawan). Skema logging dapat dijelaskan sebagai berikut:


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
/

Tabel log_inances pertama berisi informasi tentang setiap permulaan aplikasi:


  • log_instance_name - name, misalnya, "sample_app_20200501"
  • start_ts / end_ts - waktu mulai dan berakhir pencatatan ( end_ts kosong jika aplikasi sedang berjalan)
  • status - 'C' (selesai) - aplikasi dihentikan tanpa kesalahan, 'R' (berjalan) - aplikasi sedang berjalan, 'F' (gagal) - aplikasi diakhiri dengan kesalahan.
  • start_log_id adalah id root dalam hierarki panggilan.

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:


Implementasi Oracle:


  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

Implementasi Java:


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

All Articles