`

系统用户行为日志记录

阅读更多

http://hi.baidu.com/xbnh0217/item/fd78f92a010dedc1ef10f1a7

man1900.iteye.com/blog/648107

blog.csdn.net/kimylrong/article/details/7639346

http://blog.csdn.net/javazhichizhe/article/details/6468687

导读:作者Frank Wiles发 表了一篇博文,Frank Wiles曾在很多演讲里说过,改进你的系统的最好的方法是先避免做“蠢事”。并不是说你或你开发的东西“蠢”,只是有些决定很容易被人们忽略掉其暗含的 牵连,认识不到这样做对系统维护尤其是系统升级带来多大的麻烦。作为一个顾问,像这样的事情我到处都能见到,我还从来没有见过做出这样的决定的人有过好的 结果的。

 

图片,文件,二进制数据

 

既然数据库支持BLOB类型的数据,把文件塞进BLOB字段里一定没有错了!?错,不是这样的!别的先不提,在很多数据库语言里,处理大字段都不是很容易。

 

把文件存放在数据库里有很多问题:

 

  • 对数据库的读/写的速度永远都赶不上文件系统处理的速度
  • 数据库备份变的巨大,越来越耗时间
  • 对文件的访问需要穿越你的应用层和数据库层

 

这后两个是真正的杀手。把图片缩略图存到数据库里?很好,那你就不能使用nginx或其它类型的轻量级服务器来处理它们了。

 

给自己行个方便吧,在数据库里只简单的存放一个磁盘上你的文件的相对路径,或者使用S3或CDN之类的服务。

 

短生命期数据

 

使用情况统计数据,测量数据,GPS定位数据,session数据,任何只是短时间内对你有用,或经常变化的数据。如果你发现自己正在使用定时任务从某个表里删除有效期只有一小时,一天或数周的数据,那说明你没有找对正确的做事情的方法。使用redisstatsd/graphiteRiak,它们都是干这种事情更合适的工具。这建议也适用于对于收集那些短生命期的数据。

 

当然,用挖土机在后花园里种土豆也是可行的,但相比起从储物间里拿出一把铲子,你预约一台挖土机、等它赶到你的园子里挖坑,这显然更慢。你要选择合适的工具来处理手头上的事。

 

日志文件

 

把日志数据存放到数据库里,表面上看起来似乎不错,而且“将来也许我需要对这些数据进行复杂的查询”,这样的话很得人心。这样做并不是一个特别差的做法,但如果你把日志数据和你的产品数据存放到一个数据库里就非常不好了。

 

也许你的日志记录做的很保守,每次web请求只产生一条日志。对于整个网站的每个事件来说,这仍然会产生大量的数据库插入操作,争夺你用户需要的数据库资源。如果你的日志级别设置为verbose或debug,那等着看你的数据库着火吧。

 

你应该使用一些比如Splunk Loggly或纯文本文件来存放你的日志数据。这样去查看它们也许会不方便,但这样的时候不多,甚至有时候你需要写出一些代码来分析出你想要的答案,但总的来说是值得的。

 

可是稍等一下,你是那片不一样的雪花,你遇到的问题会如此的不同,所以,如果你把上面提到的三种东西中的某一种放到了数据库里也不会有问题。不,你错了,不,你不特殊。相信我。

 

译文出自:外刊IT评论

 

英文出自:revsys

/*******************************************************************************************************************/

日志文件是一个随处都在使用的文件,它可以很好的记录程序的运行状态和出错信息,几乎每一个安装程序都有它的安装文件..在我们用java开发web项目中,会经常需要记录用户的一些登陆,访问,操作信息.如:一个办公系统,它的日志文件需要记录:
………
Wed May 19 13:35:50 CST 2004:张三登陆了该系统
Wed May 19 13:35:53 CST 2004:张三插入了一条"下周计划"记录 id:1048
Wed May 19 13:35:54 CST 2004:李四登陆了该系统
Wed May 19 13:35:55 CST 2004:张三删除了一条"上周总结"记录, d:1024
Wed May 19 13:35:59 CST 2004:张三退出了该系统
................
实现思路:
1. 为了很好的实现这个记录类,必须要使用”单态”模式,这样该类不必每次调用的时候都需要生成它的实例,初始化i/o.(这在一定程度上是很耗费时间的).
2. 为了防止多线程同时操作(写)日志文件,造成文件”死锁”,必须考虑同步,使用synchronized关键字.
3. 为了不必关心该类唯一实例的生成,而直接使用该类的静态方法实现日志的记录
4. 为了更方便的配置日志文件的路径,使用属性文件配置.

废话太多了,不像搞程序的了,直接看代码,所有的注释在代码中说明:


import java.io.*;
import java.util.*;
public class LogWriter {
  private static final String DefalutLogFilePathName="c:\\logtext.log";//默认的日志文件的路径和文件名称
  private static LogWriter logwriter; //该类的唯一的实例
  private static InputStream fin; //属性配置文件的输入流
  private static Properties pro; //class Properties's supper is Hashtable class
  private static PrintWriter out; //output stream
  private static String logFileName; //output file name
  private LogWriter() {
    outInit();//init out put stream,实例化PrintWriter out 对象.
  }
  /**保存你想保存在日志文件中的信息,实现同步
   * out put the message infomation
   * @param message infomation
   */
  public static synchronized void log(String message) {
    if (logwriter == null || (out == null)){
      logwriter = new LogWriter();
    }
    if (out != null) {
      out.println(new java.util.Date() + ":" + message);
    }
  }
  /**把异常信息保存在日志文件中,实现同步
   * out put the Excetion infomation
   * @param message infomation
   */
  public static synchronized void log(Exception ex) {
    if (logwriter == null || (out == null))
      logwriter = new LogWriter();
    if (out != null) {
      out.println(new java.util.Date() + ":" );
      ex.printStackTrace(out);
    }
  }
  /**
   *输出文件流的init
   */
  private void outInit() {
    if (logFileName == null)
      logFileName = getlogFileName(); //从属性文件中类获得日志文件的路径
    try {
      if (out == null) {//如果输出i/o没有实例,则生成一个信的
        out = new PrintWriter(new FileWriter(logFileName, true), true); ; //
        //其中的FileWriter()中的第二个参数的含义是:是否在文件中追加内容
      }
    }
    catch (IOException ex) {
      System.out.println("无法打开日志文件:"+logFileName);
      ex.printStackTrace();
      out = null;
    }
  }
  /**
   *根据配置文件.来获得日志文件的位置
   *
   * @return logFileName
   */
  private String getlogFileName() {
    try {
      if (pro == null) {
        pro = new java.util.Properties();
        fin = getClass().getResourceAsStream("log.properties"); //在类的当前位置,查找属性配置文件log.properties
        pro.load(fin);//载入配置文件
        fin.close();
      }
    }
    catch (IOException ex) {
       System.err.println("无法打开属性配置文件: log.properties" );
      ex.printStackTrace();
    }
    return pro.getProperty("logfile",DefalutLogFilePathName);
    //根据属性值获得日志文件路径,第二个参数是:如果找不到"logfile"标志,就返回的默认值
  }
  /**你也可以在所有的日志都记录完成的时候,调用该方法,释放资源.
   * free all the resouce,this is secuty method
   */
  public void free() {
    try {
      this.logwriter = null;
      if (out != null)
        this.out.close();
      if (fin != null)
        this.fin.close();
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

●类的具体使用::
1.,把该类编译好, 新建立属性配置文件:log.properties,并确保把它放倒你的这个编译好的类所在的位置
文件内容如下:当然你可以把路径修改为你想要的路径
logfile=e:\\logtext.log
2.. 使用举例:
使用1:
LogWriter.log("张三登陆了该系统");
logWriter.log("张三删除了xxx条记录:记录id:");
使用2:
try{
}
catch (Exception ex) {
LogWriter.log(ex);
}
● 几点说明:
一.其中的 getClass().getResourceAsStream("文件名称")不支持static的调用,
所以要把该类换为非static,但是它的调用仅仅在于outinit()中调用,而outinit()
也仅仅在私有的构造函数中调用,而私有构造函数可以在静态的static 中被调用,
这样就达到了可以利用静态方法来调用随时输入日志,并保证了仅仅有一个实例

二.如果你了解log4J的话,可以使用该类似方法,把log4j封装一下,实现静态方便的调用.

三.我同时使用多个线程测试过该类.没有发现问题.因为对java才研究了一年.如果高手们看出其中的错误或者不足之处,也请多多赐教.谢谢.具体问题可联:qq:29189725

/*******************************************************************************************************************/

  1.    
  2. import java.io.*;   
  3. import java.util.*;   
  4. /**  
  5.   *  
  6.   * <p>Title: LOG 日志记录</p>  
  7.   * <p>Description:  
  8.   * 此类主要用来记录系统中发生的重大事件,以及由于程序本身所产生的错误信息</p>  
  9.   * <p>Copyright: Copyright (c) 2003</p>  
  10.   * <p>Company: hoten </p>  
  11.   * @author lqf  
  12.   * @version 1.0  
  13.   */   
  14. ////////////////////////////////////////////////////////////////////////////////////////////   
  15. //   
  16. ///////////////////////////////////////////////////////////////////////////////////////////   
  17. public class Log{   
  18.   /**  
  19.    * 用来记录系统重大事件  
  20.    * @param msg 重大事件  
  21.    * @param fileName 日志文件的路径及名称  
  22.    */   
  23.    
  24.     public static void printBytes(byte[] msg,String logFile){   
  25.          StringBuffer sb = new StringBuffer(100);   
  26.          for(int i=0;i<msg.length;i++){   
  27.              sb.append((int)msg[i]);   
  28.              sb.append(",");   
  29.          }   
  30.          printEvent(sb.toString(),logFile);   
  31.    }   
  32.   public synchronized static void printEvent(String msg,String fileName)   
  33.    {   
  34.       msg = new String( "时间:"+CTime.getTime(CTime.YYMMDDhhmmss) + " 事件消息:  " + msg);   
  35.       if(fileName!=null) printToFile(msg,fileName);   
  36.       else print(msg);   
  37.       return;   
  38.    }   
  39.    
  40.    public synchronized static void printError(Throwable e,String msg,String fileName)   
  41.    {   
  42.       StringBuffer errors=new StringBuffer(100);   
  43.       errors.append("时间:");   
  44.       errors.append(CTime.getTime(CTime.YYMMDDhhmmssxxx));   
  45.       errors.append(" 消息:");   
  46.       errors.append(msg);   
  47.       errors.append(" Exception: ");   
  48.       if(fileName!=null) {   
  49.             printToFile(errors.toString().trim(),fileName);   
  50.             try {   
  51.                 e.printStackTrace(new PrintWriter(new FileWriter(fileName,true),true));//   
  52.             }   
  53.             catch (Exception ex) {   
  54.             }   
  55.       }   
  56.       else print(errors.toString().trim());   
  57.       return;   
  58.    }   
  59. /**  
  60.  * 记录应程序本身发生的错误,主要给程序员观察。  
  61.  * @param e  一个Exception  
  62.  * @param mobile 用户手机号码  
  63.  * @param msg   用户发送的消息  
  64.  * @param fileName 日志文件的路径及名称  
  65.  */   
  66.    public synchronized static void printError(Throwable e,String mobile,String msg,String fileName)   
  67.    {   
  68.       StringBuffer errors=new StringBuffer(100);   
  69.       errors.append("时间:");   
  70.       errors.append(CTime.getTime(CTime.YYMMDDhhmmssxxx));   
  71.       errors.append(" 手机号码:");   
  72.       errors.append(mobile);   
  73.       errors.append(" 消息:");   
  74.       errors.append(msg);   
  75.       errors.append(" Exception: ");   
  76.       if(fileName!=null) {   
  77.             printToFile(errors.toString().trim(),fileName);   
  78.             try {   
  79.                 e.printStackTrace(new PrintWriter(new FileWriter(fileName,true),true));//   
  80.             }   
  81.             catch (Exception ex) {   
  82.             }   
  83.       }   
  84.       else print(errors.toString().trim());   
  85.       return;   
  86.    }   
  87. /**把错误消息打印到屏幕上  
  88.  *  
  89.  * @param msg 错误消息  
  90.  */   
  91.    private static void print(String msg)   
  92.    {   
  93.        System.out.println(msg);   
  94.    }   
  95. /**  
  96.  * 把消息打印到指定文件  
  97.  * @param msg 错误消息  
  98.  * @param fileName 指定的文件  
  99.  */   
  100.    
  101.    private static void printToFile(String msg,String fileName) //打印到文件中   
  102.    {   
  103.       BufferedWriter mBufWriter = null;   
  104.       try   
  105.       {   
  106.          if(!createFile(fileName)) return ;   
  107.          FileWriter fileWriter = new FileWriter(fileName, true);   
  108.          mBufWriter = new BufferedWriter(fileWriter);   
  109.    
  110.          mBufWriter.write(msg);   
  111.          mBufWriter.newLine();   
  112.    
  113.          mBufWriter.flush();   
  114.          mBufWriter.close();   
  115.       }   
  116.       catch (Throwable e)   
  117.       {   
  118.          try { mBufWriter.close(); } catch (Throwable t) {};   
  119.       }   
  120.       return;   
  121.    }   
  122. /**  
  123.  * 用来创建文件和文件夹  
  124.  * @param fileName 文件或文件夹名称  
  125.  * @return  
  126.  * @throws IOException   
  127.  * @throws Exception  
  128.  */   
  129.    
  130.   private static boolean createFile(String fileName)throws IOException ,Exception{   
  131.         File file = new File(fileName);   
  132.          if (file.exists()) /* does file exist? If so, can it be written to */   
  133.          {   
  134.             if (file.canWrite() == false)   
  135.                return false;   
  136.          }   
  137.          else   
  138.          {   
  139.            String path = null;  /* Does not exist.  Create the directories */   
  140.    
  141.            int firstSlash = fileName.indexOf(File.separatorChar);   
  142.            int finalSlash = fileName.lastIndexOf(File.separatorChar);   
  143.    
  144.            if (finalSlash == 0) { /* error, not valid path */ }   
  145.            else if (finalSlash == 1/* UNIX root dir */   
  146.            {   
  147.              path = File.separator;   
  148.            }   
  149.            else if (firstSlash == finalSlash)   
  150.            { /* for example c:\  Then make sure slash is part of path */   
  151.              path = fileName.substring(0,finalSlash+1);   
  152.            }   
  153.            else   
  154.            { path = fileName.substring(0,finalSlash); }   
  155.    
  156.            File dir = new File(path);   
  157.            dir.mkdirs();   
  158.          }   
  159.          return true;   
  160.   }  

/*******************************************************************************************************************/

  1. import java.util.*;   
  2. /**  
  3.  * <p>Title: Time  </p>  
  4.  * <p>Description: </p>  
  5.  *      此类主要用来取得本地系统的系统时间并用下面5种格式显示  
  6.  *              1. YYMMDDHH         8位  
  7.  *              2. YYMMDDHHmm       10位  
  8.  *              3. YYMMDDHHmmss     12位  
  9.  *              4. YYYYMMDDHHmmss   14位  
  10.  *              5. YYMMDDHHmmssxxx  15位 (最后的xxx 是毫秒)  
  11.  * <p>Copyright: Copyright (c) 2003</p>  
  12.  * <p>Company: hoten </p>  
  13.  * @author lqf  
  14.  * @version 1.0  
  15.  */   
  16. public class CTime {   
  17.     public static final int YYMMDDhhmmssxxx=15;   
  18.     public static final int YYYYMMDDhhmmss=14;   
  19.     public static final int YYMMDDhhmmss=12;   
  20.     public static final int YYMMDDhhmm=10;   
  21.     public static final int YYMMDDhh=8;   
  22. /**  
  23.  * 取得本地系统的时间,时间格式由参数决定  
  24.  * @param format 时间格式由常量决定  
  25.  * @return String 具有format格式的字符串  
  26.  */   
  27.     public synchronized static String  getTime(int format){   
  28.         StringBuffer cTime=new StringBuffer(10);   
  29.         Calendar time=Calendar.getInstance();   
  30.         int miltime=time.get(Calendar.MILLISECOND);   
  31.         int second=time.get(Calendar.SECOND);   
  32.         int minute=time.get(Calendar.MINUTE);   
  33.         int hour=time.get(Calendar.HOUR_OF_DAY);   
  34.         int day =time.get(Calendar.DAY_OF_MONTH);   
  35.         int month=time.get(Calendar.MONTH)+1;   
  36.         int year =time.get(Calendar.YEAR);   
  37.         if(format!=14){   
  38.             if(year>=2000) year=year-2000;   
  39.             else year=year-1900;   
  40.         }   
  41.         if(format>=2){   
  42.             if(format==14) cTime.append(year);   
  43.             else    cTime.append(getFormatTime(year,2));   
  44.         }   
  45.         if(format>=4)   
  46.             cTime.append(getFormatTime(month,2));   
  47.         if(format>=6)   
  48.             cTime.append(getFormatTime(day,2));   
  49.         if(format>=8)   
  50.             cTime.append(getFormatTime(hour,2));   
  51.         if(format>=10)   
  52.             cTime.append(getFormatTime(minute,2));   
  53.         if(format>=12)   
  54.             cTime.append(getFormatTime(second,2));   
  55.         if(format>=15)   
  56.             cTime.append(getFormatTime(miltime,3));   
  57.         return cTime.toString();   
  58.     }   
  59. /**  
  60.  * 产生任意位的字符串  
  61.  * @param time 要转换格式的时间  
  62.  * @param format 转换的格式  
  63.  * @return String 转换的时间  
  64.  */   
  65.     private synchronized static String getFormatTime(int time,int format){   
  66.         StringBuffer numm=new StringBuffer();   
  67.         int length=String.valueOf(time).length();   
  68.    
  69.         if(format<length) return null;   
  70.    
  71.         for(int i=0 ;i<format-length ;i++){   
  72.             numm.append("0");   
  73.         }   
  74.         numm.append(time);   
  75.         return numm.toString().trim();   
  76.     }   
  77. }  

 

 

/*******************************************************************************************************************/

系统操作日志

1934人阅读 评论(1) 收藏 举报

   1、  我想实现:

  我想设置一个JSP程序,只要有用户登陆网站(管理员或非法的),不管登录成功或失败都把登陆的用户名(登陆信息,执行页面)记录到数据库中,还有登陆后 台后对网站的修改也要做个记录,比如修改过新闻,删除留言,修改过网站配置等都有记录,不要求可恢复,能记录七天,或是1000条记录这样就可以了.多的 记录可以自动删除....类似的功能该如何实现?

   ————思路:

  a, 写个公用函数:
function doRecordLog(username, action, content)
    ' 该函数内部实现记录用户操作日志功能
end function
 b, 各页面需要记录日志的操作都调用这个函数.

————思路2:

   四种办法:

a.每页生成一个不同session,如果该页结束,将值付给变量传递到数据库保存。
b.为每条记录设置新建时的日期,修改时的日期,分别保存在数据库中,显示在管理页面,如果看到哪条记录是新修改的,就会是新的日期,新建的日期是不会变的。
c.放置一个访问统计页,统计每位后台管理者访问过的路径,我有用访问统计,能查看到来者的每个脚印,和对方的IP,不过是用在前台,当然后台也可以用的。
d.如果你想控制管理者的操作权限,可以设置多个管理组,为不同的组设置不同的管理权限,就像window一样,有管理员权限和guest权限,这样有些用户只能看而不能修改。

我就一直用第二种办法,查看更新情况的。

 

CREATE TABLE eventlog(
cid int primary key not null,
csort varchar(50) null,
cusername varchar(50) null,
ctime datetime,
cdo varchar(250) null,
cip varchar(20)
)

程序
先调用数据库
然后
<%
Function events(csort,cusername,cdo)
cip = Request.ServerVariables("HTTP_X_FORWARDED_FOR")
If cip = "" Then
cip = Request.ServerVariables("REMOTE_ADDR")
End If
ctime=now()
safesql="select * from tb_eventlog"
set rsyss=server.CreateObject("adodb.recordset")
rsyss.open safesql,con,1,3
rsyss.addnew
rsyss("csort")=csort
rsyss("cusername")=cusername
rsyss("cdo")=cdo
rsyss("ctime")=ctime
rsyss("cip")=cip
rsyss.update
rsyss.close
End Function
%>

 

不就写日记吗?

一般比较合理的作法是,先将操作中进行一闪的格式。
比如:一个管理员对数据库做了一些调整,他删除了某一个用户,那么在删除用户的同时应该向日志中记录。

这个肯定是管理员,首先它是登陆的,那么可以用session("userID")保存其登陆状态的,同时取出该值,向日记数据库中记录,这样首先做到了定位人的目的,当然这个操作是在删除用户中进行的,那么可以在动作中注明的。所以数据库的建立很清楚了:
userid      datetime     active
userid是用存放是谁做的动作
datetime用来存放在何时的动作
active用来指明动作,比如删除用户等文本方式存储
如有需要可以对该数据表进行扩充。
管理员在删除用户时,先将动作写入了日记数据库,然后才开始做删除用户的动作。这样完全记录了管理员的动作。

访问网页,可以在登陆后进行的。
比如有一个网页是abc.asp
那么在abc.asp最开始的地方测试session("userid")如果存在则,向日记数据库中插入记录,userid插入 session("userid"),当前时间插入到datetime中,将字符串“访问了abc网页”插入到active中,这样你取出记录可以看到, 某一用户在某一时间,访问了abc网页。当然如果访问网页过多,可以将这一段写出来,导入到每个网页中。就可以了!

至于怎么控制网页的开始,这是一个重定向的问题。
比如,你在后台中写了关闭网站中的某个页面,则可以用一个application("vis")变量来控制。点击关闭时,执行了一个事 件,application("vis")="false",反之,将这个值取为true,在application-start事件中先义其打开或关 闭。

运行该网页时最开始,去取application("vis")的值,若是true,则显示该页,不用有动作,若是false则将网页定向到另一个提示网页中!

当然,我这里是以application为例的,其实你也可以存入数据库中的,所以存入的作为配置。这样即使重启服务器也不会丢失的!只不过这里的原理就是这样!

简单不?它和写普通页面没有什么区别的!所谓的后台只不过是管理员登陆的!实际上对于开发来说是不分前台后台的。这个名词只是网页功能的区分而已!

在执行操作或进入某页时调用
<%'如在用户登录时
events "用户登录",session("username"),"成功登录后台管理"

'在信息修改时
events "企业荣誉",session("username"),"修改荣誉分类名称"

%>这样子就可以了

第二个没看懂你说的是什么意思
那你在页面前面加一个判断不就行了
先设定一个表
页面  值(0/1)
在每页

if Request.ServerVariables("SERVER_PORT")<>"80" then
     userurl = "http://"&Request.ServerVariables("SERVER_NAME")& ":" & Request.ServerVariables("SERVER_PORT")& Request.ServerVariables("URL")
    else
     userurl = "http://"&Request.ServerVariables("SERVER_NAME")& Request.ServerVariables("URL")
end if
取你的网址
userurl=right(UserUrl,InstrRev(UserUrl,"/")+1)取你网址中的网页名
然后
读你表里网页名相同的 在进行判断 如果值为0就是关闭了如果值为1就是可以看
你后台里在做一个页来控制页是否关闭就行了

   ——————————操作日志————————

    a、 日志里大概会记录:谁在什么时间什么地方做了什么操作
所以写一个类似的过程,addlog(user,time,place,operate)在需要记录的地方调用即可.
比如要记录登陆过程,则在登陆完成之后记录,登陆的用户名,时间,(这里地方可以不记录了,因为应该只有一个登陆点吧),操作则是登陆.
其他地方类似的如此调用去记录.

b、 我是放在一个单独的日志文件里的,按日期来命名
procedure WriteToTxt(content:string);
var
F: Textfile;
path,name:string;
begin
  name:=FormatDateTime('yyyy-mm-dd',now);//取得日期
    path:=ExtractFilePath(Application.ExeName)+'log/'+ name+'.txt';//日志存取路径
    AssignFile(F,path);
    if FileExists(path) =False then
    ReWrite(F);
    Append(F);
    Writeln(F,'['+DateTimeToStr(Now())+']:'+content); //写入日志文件
    Closefile(F);
end;

在具体的操作后面调用就OK了

 

 在OA项目中,需要记录用户都进行了什么操作,事件包括增加、删除和修改,希望记录成类似以下格式:

用户XXX在2007年X月X日增加了一个产品类别XXX
用户XXX在2007年X月X日删除了一个用户XXX

现在请教大家,除了在增删改的时候另外再运行一个存储过程记录这些信息外,有没有其他什么更简单的方法啊,感谢赐教~~感谢参与~

——————解答:

用文本文件记录吧,用追加文本    我的习惯做法是在数据库中建立日志表。
再编写一些方法,在用户进行增删改查时触发。
向表中添加数据           

  可以考虑通过   HttpModule   统一处理,
但是,你得考虑如何捕获用户进行了什么操作

/*******************************************************************************************************************/

JAVA反射与AOP双剑合璧详细记录操作日志

分类: Core Java 5441人阅读 评论(0) 收藏 举报

       运用AOP来记录用户的操作日志在项目中比较常见,优点是只需在一个地方编写Advice,通过AOP声明(织入)然后就可以记录很多不同的操作 (API)。但是也有其缺点,因为Advice服务于不同的API,而各个API的参数,返回值不同,甚至服务的对象都不一样,那么能做到的也只能是判断 是否有异常,异常的具体信息等简单的内容。如果想要个性化的为每一个API都记录执行参数,返回值,甚至Target的属性时就无能为力,因为能够拿到对 象,但是不知道到底是什么类型的。本文就利用Java反射机制来获取这些信息。

 

       首先要能使用反射,那么就必须知道方法或者属性名称,所以需要配置相关信息。本示例中用Bean的属性(Field)来配置。一个操作对应一个API, 由开发人员配置。最后记录的信息以key=value的形式保存,key就是key,容易理解,value是指对对象(参数和返回值两种)的field属 性值,如果嵌套多层则用.(dot)隔开。值得注意的的参数的情形,[0]表示第一个参数,[1]表示第二个参数。之所以使用序号是由于Java参数名在 编译完之后会丢失,如果AOP使用AspectJ编译的话可以保存。XML文件具体设置如下:

  1. <apis>  
  2.      <api name="ServerWebServiceImp.login">  
  3.       <params>  
  4.         <param key="user_name" value="[0]" />  
  5.         <param key="password" value="[1]" />  
  6.       </params>  
  7.       <returns>  
  8.         <return key="aspclient_id" value="ASPClient_Id" />  
  9.         <return key="user_id" value="users_Id" />  
  10.       </returns>  
  11.       </api>  
  12. </apis>  

需要把XML解析为JavaBean存到内存里面,解析引擎有很多选择,如Dom4j等。具体的解析过程不贴了,很简单,JavaBean如下:

  1. /** 
  2.  * API实体类 
  3.  */  
  4. public class ApiEntity {  
  5.     // 名称  
  6.     private String name;  
  7.     // API的参数  
  8.     private List<Pair> params;  
  9.     // API返回值  
  10.     private List<Pair> returns;  
  11.   
  12.     public ApiEntity() {  
  13.         params = new ArrayList<ApiEntity.Pair>();  
  14.         returns = new ArrayList<ApiEntity.Pair>();  
  15.     }  
  16.   
  17.     public void addParam(ApiEntity.Pair pair) {  
  18.         params.add(pair);  
  19.     }  
  20.       
  21.     public void addReturn(ApiEntity.Pair pair) {  
  22.         returns.add(pair);  
  23.     }  
  24.   
  25.     public String getName() {  
  26.         return name;  
  27.     }  
  28.   
  29.     public void setName(String name) {  
  30.         this.name = name;  
  31.     }  
  32.   
  33.     public List<Pair> getParams() {  
  34.         return params;  
  35.     }  
  36.   
  37.     public void setParams(List<Pair> params) {  
  38.         this.params = params;  
  39.     }  
  40.   
  41.     public List<Pair> getReturns() {  
  42.         return returns;  
  43.     }  
  44.   
  45.     public void setReturns(List<Pair> returns) {  
  46.         this.returns = returns;  
  47.     }  
  48.   
  49.     public static class Pair {  
  50.         private String key;  
  51.         private String value;  
  52.   
  53.         public String getKey() {  
  54.             return key;  
  55.         }  
  56.   
  57.         public void setKey(String key) {  
  58.             this.key = key;  
  59.         }  
  60.   
  61.         public String getValue() {  
  62.             return value;  
  63.         }  
  64.   
  65.         public void setValue(String value) {  
  66.             this.value = value;  
  67.         }  
  68.     }  
  69. }  

 

         上面是说明如何设置以及表示API,下面就需要利用设置的内容来具体处理参数以及返回值。通常我们记录的是JavaBean的属性值,但是也会出现 List,Map,甚至HttpSession的Attribute。为了能够处理所有的内容,我们需要设置一个插件的钩子,有特殊需求的人可以自己去处 理获取对象内容的过程。声明一个接口:

/**
 * 如何获取对象的值或者属性值接口
 */
public interface ValueGetter {
/**
* 判断是否可以处理该对象的属性值

* @param target
*            需要处理的对象,已经保证不会为NULL
* @param key
*            配置文件中的key值,起辅助作用
* @param fieldName
*            对应的属性名称,已经保证不会为空
* @return
*/
Boolean canGet(Object target, String key, String fieldName);


/**
* 获取对象的属性值

* @param target
*            需要处理的对象,已经保证不会为NULL
* @param fieldName
*            对应的属性名称,已经保证不会为空
* @return
*/
Object getValue(Object target, String fieldName);
}

 

具有特殊需求的开发人员可以实现该接口并注册,那么框架自动会调用个性化的实现。下面是普通的JavaBean实现,框架自己提供的,主要用到Java的反射机制:

  1. /** 
  2.  * 默认的JavaBean获取属性实现. 支持处理私有的,父类的属性 
  3.  */  
  4. public class JavaBeanValueGetter implements ValueGetter {  
  5.     @Override  
  6.     public Boolean canGet(Object target, String key, String fieldName) {  
  7.         Field field = getField(target.getClass(), fieldName);  
  8.         if (field != null) {  
  9.             return true;  
  10.         } else {  
  11.             return false;  
  12.         }  
  13.     }  
  14.   
  15.     @Override  
  16.     public Object getValue(Object target, String fieldName) {  
  17.         Field field = getField(target.getClass(), fieldName);  
  18.         Object fieldValue = null;  
  19.   
  20.         if (field != null) {  
  21.             field.setAccessible(true);  
  22.             try {  
  23.                 fieldValue = field.get(target);  
  24.             } catch (Exception e) {  
  25.                 e.printStackTrace();  
  26.             }  
  27.         }  
  28.   
  29.         return fieldValue;  
  30.     }  
  31.   
  32.     @SuppressWarnings("rawtypes")  
  33.     private Field getField(Class c, String fieldName) {  
  34.         Field field = null;  
  35.   
  36.         try {  
  37.             field = c.getDeclaredField(fieldName);  
  38.         } catch (NoSuchFieldException e) {  
  39.             Class parentClass = c.getSuperclass();  
  40.             if (parentClass != null) {  
  41.                 field = getField(parentClass, fieldName);  
  42.             }  
  43.         }  
  44.   
  45.         return field;  
  46.     }  
  47. }  

Around的Advice实现我也不贴了,主要用Spring的AOP实现。该Advice里面会维护一个 List<ValueGetter>,并且BeanValueGetter为第一个(效率的考虑,用得最多的是 BeanValueGetter)。下面举一个最简单的例子:
  1. public class Customer {  
  2.     private String name;  
  3.     private Address address;  
  4.   
  5.     public String getName() {  
  6.         return name;  
  7.     }  
  8.   
  9.     public void setName(String name) {  
  10.         this.name = name;  
  11.     }  
  12.   
  13.     public Address getAddress() {  
  14.         return address;  
  15.     }  
  16.   
  17.     public void setAddress(Address address) {  
  18.         this.address = address;  
  19.     }  
  20. }  

  1. public class Address {  
  2.     private String roadNo;  
  3.   
  4.     public String getRoadNo() {  
  5.         return roadNo;  
  6.     }  
  7.   
  8.     public void setRoadNo(String roadNo) {  
  9.         this.roadNo = roadNo;  
  10.     }  
  11. }  


API为 public Long saveCustomer(Customer customer);

配置文件为

  1. <api name="saveCustomer">  
  2.     <params>  
  3.         <param key="roadNo" value="[0].address.roadNo" />  
  4.     </params>  
  5.     <returns>  
  6.         <return key="customer_id" value="" />  
  7.     </returns>  
  8. </api>  

特别说明: .表示的嵌套属性先分割开再处理,处理的逻辑单元为没有点的情形,逐级调用,roadNo调用了两次BeanValueGetter。value为空表示对象自己,不再获取属性值。

最后的描述信息可能为:saveCustomer{params(roadNo=1024),returns(customer_id=67)}

 

  上面只是展示了最简单的情形,你可以添加更多的API属性,比如API描述信息,所属模块等。总之就是使用XML配置+Java反射+AOP详细记录操作日志信息。

/*******************************************************************************************************************/

Spring AOP一直是Spring的一个比较有特色的功能,利用它可以在现有的代码的任何地方,嵌入我们所想的逻辑功能,并且不需要改变我们现有的代码结构。

 

鉴于此,现在的系统已经完成了所有的功能的开发,我们需要把系统的操作日志记录起来,以方便查看某人某时执行了哪一些操作。Spring AOP可以方便查看到某人某时执行了哪一些类的哪一些方法,以及对应的参数。但是大部分终端用户看这些方法的名称时,并不知道这些方法名代码了哪一些操 作,于是方法名对应的方法描述需要记录起来,并且呈现给用户。我们知道,AOP拦截了某些类某些方法后,我们可以取得这个方法的详细定义,通过详细的定 义,我们可以取得这个方法对应的注解,在注解里我们就可以比较方便把方法的名称及描述写进去。于是,就有以下的注解定义。代码如下所示:

 

Java代码  收藏代码
  1. package com.htsoft.core.log;  
  2.   
  3. import java.lang.annotation.Documented;  
  4. import java.lang.annotation.ElementType;  
  5. import java.lang.annotation.Inherited;  
  6. import java.lang.annotation.Retention;  
  7. import java.lang.annotation.RetentionPolicy;  
  8. import java.lang.annotation.Target;  
  9.   
  10. /** 
  11.  * @company  广州宏天软件有限公司 
  12.  * @description 类的方法描述注解 
  13.  * @author csx 
  14.  * @create 2010-02-03 
  15.  */  
  16. @Target(ElementType.METHOD)     
  17. @Retention(RetentionPolicy.RUNTIME)     
  18. @Documented    
  19. @Inherited    
  20. public @interface Action {  
  21.     /** 
  22.      * 方法描述 
  23.      * @return 
  24.      */  
  25.     public String description() default "no description";   
  26. }  

 

 

在我们需要拦截的方法中加上该注解:

 

 

Java代码  收藏代码
  1. /** 
  2.  *  
  3.  * @author csx 
  4.  *  
  5.  */  
  6. public class AppUserAction extends BaseAction {   
  7.     /** 
  8.      * 添加及保存操作 
  9.      */  
  10.     @Action(description="添加或保存用户信息")  
  11.     public String save() {  
  12.              ....  
  13.         }  
  14.        /** 
  15.      * 修改密码 
  16.      *  
  17.      * @return 
  18.      */  
  19.     @Action(description="修改密码")  
  20.     public String resetPassword() {  
  21.               ....  
  22.         }  
  23. }  

 

现在设计我们的系统日志表,如下所示:

 

设计嵌入的逻辑代码,以下类为所有Struts Action的方法都需要提前执行的方法。(对于get与set的方法除外,对于没有加上Action注解的也除外)

 

Java代码  收藏代码
  1. package com.htsoft.core.log;  
  2.   
  3. import java.lang.reflect.Method;  
  4. import java.util.Date;  
  5.   
  6. import javax.annotation.Resource;  
  7.   
  8. import org.apache.commons.lang.StringUtils;  
  9. import org.apache.commons.logging.Log;  
  10. import org.apache.commons.logging.LogFactory;  
  11. import org.aspectj.lang.ProceedingJoinPoint;  
  12.   
  13. import com.htsoft.core.util.ContextUtil;  
  14. import com.htsoft.oa.model.system.AppUser;  
  15. import com.htsoft.oa.model.system.SystemLog;  
  16. import com.htsoft.oa.service.system.SystemLogService;  
  17.   
  18. public class LogAspect {  
  19.       
  20.     @Resource  
  21.     private SystemLogService systemLogService;  
  22.       
  23.     private Log logger = LogFactory.getLog(LogAspect.class);  
  24.   
  25.     public Object doSystemLog(ProceedingJoinPoint point) throws Throwable {  
  26.   
  27.         String methodName = point.getSignature().getName();  
  28.   
  29.         // 目标方法不为空  
  30.         if (StringUtils.isNotEmpty(methodName)) {  
  31.             // set与get方法除外  
  32.             if (!(methodName.startsWith("set") || methodName.startsWith("get"))) {  
  33.   
  34.                 Class targetClass = point.getTarget().getClass();  
  35.                 Method method = targetClass.getMethod(methodName);  
  36.   
  37.                 if (method != null) {  
  38.   
  39.                     boolean hasAnnotation = method.isAnnotationPresent(Action.class);  
  40.   
  41.                     if (hasAnnotation) {  
  42.                         Action annotation = method.getAnnotation(Action.class);  
  43.                           
  44.                         String methodDescp = annotation.description();  
  45.                         if (logger.isDebugEnabled()) {  
  46.                             logger.debug("Action method:" + method.getName() + " Description:" + methodDescp);  
  47.                         }  
  48.                         //取到当前的操作用户  
  49.                         AppUser appUser=ContextUtil.getCurrentUser();  
  50.                         if(appUser!=null){  
  51.                             try{  
  52.                                 SystemLog sysLog=new SystemLog();  
  53.                                   
  54.                                 sysLog.setCreatetime(new Date());  
  55.                                 sysLog.setUserId(appUser.getUserId());  
  56.                                 sysLog.setUsername(appUser.getFullname());  
  57.                                 sysLog.setExeOperation(methodDescp);  
  58.                                   
  59.                                 systemLogService.save(sysLog);  
  60.                             }catch(Exception ex){  
  61.                                 logger.error(ex.getMessage());  
  62.                             }  
  63.                         }  
  64.                           
  65.                     }  
  66.                 }  
  67.   
  68.             }  
  69.         }  
  70.         return point.proceed();  
  71.     }  
  72.   
  73. }  
 

通过AOP配置该注入点:

 

Java代码  收藏代码
  1. <aop:aspectj-autoproxy/>   
  2. <bean id="logAspect" class="com.htsoft.core.log.LogAspect"/>    
  3.  <aop:config>  
  4.         <aop:aspect ref="logAspect">  
  5.             <aop:pointcut id="logPointCut" expression="execution(* com.htsoft.oa.action..*(..))"/>  
  6.             <aop:around pointcut-ref="logPointCut" method="doSystemLog"/>  
  7.         </aop:aspect>  
  8. </aop:config>  
 

  注意,由于AOP的默认配置是使用代理的方式进行嵌入代码运行,而StrutsAction中若继承了ActionSupport会报错误,错误是由于其使用了默认的实现接口而引起的。所以Action必须为POJO类型。

 

如我们操作了后台的修改密码,保存用户信息的操作后,系统日志就会记录如下的情况。

 

 

 

/*******************************************************************************************************************/

Spring MVC 拦截Action 获取action中的参数并转码(续)

 

Spring拦截类

package com.teamsun.aop;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.teamsun.common.EnCodingUtil;
import com.teamsun.req.ParameterRequestWrapper;

public class ActionBeforeHandler implements HandlerInterceptor {

 @Override
 public void afterCompletion(HttpServletRequest arg0,
   HttpServletResponse arg1, Object arg2, Exception arg3)
   throws Exception {
  System.out.println("========afterCompletion=========================");

 }

 @Override
 public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
   Object arg2, ModelAndView arg3) throws Exception {
  
  System.out.println("========postHandle=========================");
 }

 @Override
 public boolean preHandle(HttpServletRequest req, HttpServletResponse res,
   Object arg2) throws Exception {
  
  Map map =req.getParameterMap();//获取页面提交到action前参数
  
  Map param = new HashMap<String, Object>();//将参数存放到新的的map中
  
  if(map!=null && !map.isEmpty()){
   
   Set<String> keys =map.keySet();
   
   Iterator<String> it = keys.iterator();
   while(it.hasNext()){
    String key = it.next();
    Object t =map.get(key);
    
    if(t instanceof String){//给String转码
     if(t != null){
      t =  EnCodingUtil.getValue((String)t);
     }
    }if(t instanceof String[]){//给String数组转码
     if(t != null){
      String args[] = (String[])t;
      for (int i = 0; i < args.length; i++) {
       args[i] =  EnCodingUtil.getValue(args[i]);//转码类
      }
       t = args;
     }
    }else {//给类转码
     t =  EnCodingUtil.getClass(t);
    }
    
    param.put(key, t);
   }
  }
  
  ParameterRequestWrapper p = new ParameterRequestWrapper(req,param);//使用新建的参数类,不然系统向原有的参数map中存放参数报错
  
  System.out.println("拦截成功");
  System.out.println("========进入方法开始=========================");
  System.out.println("进入类"+arg2.getClass().getName()+""+req.getParameter("dept"));
  
  System.out.println("========进入方法结束=========================");
  return true;
 }

}

 

ParameterRequestWrapper 

 

package com.teamsun.req;

import java.util.Enumeration;
import java.util.Map;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * 用来存放jsp提交到action中的参数
 * @author Administrator
 *
 */
public class ParameterRequestWrapper extends HttpServletRequestWrapper {

 public ParameterRequestWrapper(HttpServletRequest request) {
  super(request);

 }

 private Map params;

 public ParameterRequestWrapper(HttpServletRequest request, Map newParams) {
  super(request);
  this.params = newParams;
 }

 public Map getParameterMap() {
  return params;
 }

 public Enumeration getParameterNames() {
  Vector l = new Vector(params.keySet());
  return l.elements();
 }

 public String[] getParameterValues(String name) {
  Object v = params.get(name);
  if (v == null) {
   return null;
  } else if (v instanceof String[]) {
   return (String[]) v;
  } else if (v instanceof String) {
   return new String[] { (String) v };
  } else {
   return new String[] { v.toString() };
  }
 }

 public String getParameter(String name) {
  Object v = params.get(name);
  if (v == null) {
   return null;
  } else if (v instanceof String[]) {
   String[] strArr = (String[]) v;
   if (strArr.length > 0) {
    return strArr[0];
   } else {
    return null;
   }
  } else if (v instanceof String) {
   return (String) v;
  } else {
   return v.toString();
  }

 }
}

 

Spring AOp配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:jee="http://www.springframework.org/schema/jee"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xsi:schemaLocation="
            http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd
            http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.0.xsd
            http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd
            http://www.springframework.org/schema/jeehttp://www.springframework.org/schema/jee/spring-jee-3.0.xsd
            http://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"
 default-autowire="byName" default-lazy-init="true">
 
 
 <bean id="beforeAdvice" class="com.teamsun.aop.BeforeAdvice"></bean>
 <bean id="afterAdvice" class="com.teamsun.aop.AfterAdvice"></bean>
 <bean id="interceptorAdvice" class="com.teamsun.aop.Compareterceptor"></bean>
 
  
  <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
  <aop:config>
  <aop:pointcut id="actionOp"
   expression="execution(* *..action..*Action*.*(..)) " />
   
   <aop:pointcut id="serviceOperation"
   expression="execution(* *..teamsun..*Impl*.*(..)) " />

  <aop:advisor pointcut-ref="actionOp"
   advice-ref="beforeAdvice" />
  <aop:advisor pointcut-ref="serviceOperation"
   advice-ref="beforeAdvice" />

 </aop:config>
 <context:annotation-config />
 <context:component-scan base-package="com.**.student" />
 <context:component-scan base-package="com.**.impl" />
 <context:component-scan base-package="com.**.action" />
 
 
<!--  <bean id="urlMapping" -->
<!--   class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> -->
<!--   <property name="mappings"> -->
<!--    -->
<!--   <props> -->
<!--   <prop key="/AddAction.do">addAction</prop> -->
<!--   </props> -->
<!--   </property> -->
<!--   <property name="interceptors"> -->
<!--   <list> -->
<!--   <bean class="com.teamsun.aop.ActionBeforeHandler"/> -->
<!--   </list> -->
<!--   </property> -->
<!-- </bean>-->
<mvc:interceptors>
 <bean class="com.teamsun.aop.ActionBeforeHandler"></bean>
</mvc:interceptors>

</beans>

分享到:
评论

相关推荐

    panalog 日志管理系统

    panalog 配合panabit 的日志管理 查询 记录用户行为日志系统 满足网安的要求是运营商必选的系统

    基于Web应用的安全日志审计系统研究与设计.pdf

    件, 如客户端对服务器的访问请求记录、 黑客对网站的入侵行为记录等, 因此要想有效的管理维护设备和在攻击事件发生后及时的降低凤险, 分析审计日志对于事后检测和维护设备的安全 是非常必要的。 基于此, 文章...

    scribe日志系统文档

    scribe日志系统使用文档 本文档描述scribe日志的发送,日志内容分析,用户行为的统计,系统错误信息的记录和统计内容和错误内容的查询

    JAVA项目日志文件模版

    安全审计:日志文件可以记录用户的操作行为、权限控制和安全事件,用于审计和检测潜在的安全问题。 运维和监控:日志文件是系统运维和监控的重要依据,可以帮助运维人员了解系统的运行状态、检测异常情况并及时采取...

    操作系统安全审计.doc

    风险评估 " "测试对象: RedHat " "测试类: 安全审计 " "测试项: " "测试内容: " "确信对系统的主要行为都有审计日志,对于重要服务(ftp ,telnet " ",http)及重要操作(su登录等操作)由日志记录。...

    基于Python入侵检测,取证window日志系统框架 html + css + jquery + python 3.9

    实时监控计算机日志,如果发生以下行为取证规则,系统将语音警告提示管理员,和网管,并记录行为入库。前端显示。 用户类型 管理员 admin 123456 模块介绍 登录模型 系统首页 分析结果 取证规则 (crud,常用注意...

    网御上网行为管理系统-用户手册 20190927.pdf

    4.4 系统日志查询 50 4.5 操作日志查询 52 4.6 控制日志查询 54 4.7 审计日志查询 60 4.8 安全日志查询 83 4.9 终端日志 90 4.10 日志导出 94 4.11 日志典型配置举例 96 5 统计报表 100 5.1 简介 100 5.2 报表管理 ...

    基于Python入侵检测,取证window日志系统,框架 html + css + jquery + python 3.9

    实时监控计算机日志,如果发生以下行为取证规则,系统将语音警告提示管理员,和网管,并记录行为入库。前端显示。 用户类型 管理员 admin 123456 模块介绍 登录模型 系统首页 分析结果 取证规则 (crud,常用注意...

    前端素材推荐实用的后台管理系统liner平台模板(附带源码)

    后台管理系统网站是指用于管理和控制网站、应用程序或系统后台运行的管理工具。它通常是网站或应用程序...日志记录:系统会记录管理员和用户的操作日志,帮助管理员追踪用户行为、操作记录,及时发现问题并采取措施。

    前端素材推荐实用的后台管理系统ebazer平台模板(附带源码)

    后台管理系统网站是指用于管理和控制网站、应用程序或系统后台运行的管理工具。它通常是网站或应用程序...日志记录:系统会记录管理员和用户的操作日志,帮助管理员追踪用户行为、操作记录,及时发现问题并采取措施。

    论文研究-基于主干提取的日志自更新分类算法 .pdf

    基于主干提取的日志自更新分类算法,李怡雯,韩静,现代计算机系统会生成大规模日志数据,记录了用户行为、系统状态等重要信息。为了更好地通过日志对系统进行分析与优化,本文提出

    Python之日志处理(logging模块).docx

    通过log的分析,可以方便用户了解系统或软件...如果应用的日志信息足够详细和丰富,还可以用来做用户行为分析,如:分析用户的操作行为、类型洗好、地域分布以及其它更多的信息,由此可以实现改进业务、提高商业利益。

    威盾网络行为安全管理资料

    基本日志 对客户端PC的开机/关机,用户登入/登出,拨号等的事件记录。 基本策略 设置客户端计算机的本地系统的操作权限,包括控制面板,计算机管理,系统管理,网络属性,插件管理等所有计算机属性。并能够将客户端...

    网络安全审计.pdf

    网络安全审计概念 安全审计的概念及目的 计算机网络安全审计(Audit)是指按照一定的安全策略,利用 记录、系统活动和用户活动等信息,检查、审查和检验操作事件的环 境及活动,从而发现系统漏洞、入侵行为或改善...

    TIMO后台管理系统,通用后台管理系统

    6.行为日志:用于记录用户对系统的操作,同时监视系统运行时发生的错误。 7.文件上传:内置了文件上传接口,方便开发者使用文件上传功能。 8.代码生成:可以帮助开发者快速开发项目,减少不必要的重复操作,花更多...

    Timo后台管理系统(mysql版).zip

    行为日志:用于记录用户对系统的操作,同时监视系统运行时发生的错误。 文件上传:内置了文件上传接口,方便开发者使用文件上传功能。 代码生成:可以帮助开发者快速开发项目,减少不必要的重复操作,花更多精力注重...

    天锐绿盾打印审计系统 v1.00.zip

    对用户使用打印机进行授权管理,监督、控制用户打印行为,并提供打印内容还原审计,及详细的打印日志统计报表。  天锐绿盾打印审计系统可根据组织架构进行部门设置和用户管理,进而对企业内部所有打印机进行分配与...

    为什么要进行日志测试和如何进行日志测试

    1.在分布式的、可扩展的系统(通常包含不稳定的基础设施)中排除故障的效率通常取决于是否有充分的日志记录和搜索设备。2.唯一事件ID、事务追踪技术和结构化的日志输出等技术,让我们得以透彻地了解应用程序的行为,...

    SentCMS网站管理系统 v3.0 beta.zip

    支持自定义用户行为,可以对单个用户或群体用户的行为进行记录及分享,为您的运营决策提供有效参考数据。 文档模型/分类体系 通过和文档模型绑定,以及不同的文档类型,不同分类可以实现差异化的功能,轻松实现...

    LaneCat网猫上网行为管理软件 v2.1.1608.2400 内网版.zip

    LaneCat网猫上网行为管理软件内网版是一款基于B/S架构电信级的互联网监控软件,软件可以实时记录局域网内计算机所有用户的在使用计算机时的行为,让你能直观的了解到用户在工作中使用计算机、修改资料和访问互联网的...

Global site tag (gtag.js) - Google Analytics