`

系统用户行为日志记录

阅读更多

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>

分享到:
评论

相关推荐

    unix系统日志系统记录的日志

    `/var/adm/utmp` 是一个非常重要的系统日志文件,用于记录当前登录到系统的用户的详细信息。这个文件包含了所有终端的状态信息,包括但不限于用户ID、终端类型、登录时间等。值得注意的是,当某个用户的shell被不...

    互联网用户行为日志数据集.rar

    标题中的“互联网用户行为日志数据集.rar”表明这是一个关于互联网用户行为的大型数据集,以RAR压缩格式存储。RAR是一种流行的压缩格式,用于减少文件大小以便于存储和传输。通常,这种数据集包含了大量用户的在线...

    基于hadoop的离线用户行为分析(日志处理).zip

    综上所述,基于Hadoop的离线用户行为分析首先需要收集用户行为日志,这些日志可能包含用户的点击流、浏览时间、购买行为等信息。然后,通过Hadoop的HDFS将日志数据分布式存储,利用MapReduce或更现代的处理引擎如...

    labview操作logger,日志记录

    在LabVIEW中,"logger"通常指的是日志记录功能,用于捕获和存储程序运行时的各种信息,如变量值、错误信息、事件触发等,这对于调试、优化和分析程序行为至关重要。 日志记录在LabVIEW中的实现可以通过自定义VI...

    ASP.NET(C#)日志记录系统

    ASP.NET(C#)日志记录系统是Web应用程序中一个至关重要的组成部分,主要用于追踪和记录程序运行过程中的事件、错误和用户行为。这个系统在登录模块的基础上增加了日志记录功能,使得开发者可以更全面地了解应用的运行...

    模拟行为日志数据的生成

    7. **应用场景**:模拟行为日志数据广泛应用于广告推荐系统、用户行为分析、网络安全分析、系统性能测试等多个领域。 综上所述,模拟行为日志数据的生成是一项复杂且重要的任务,涉及多个领域的知识,包括概率统计...

    在MVC中记录操作日志类

    这些记录有助于排查错误,了解用户行为,以及在必要时进行回溯操作。 3. **在MVC中实现操作日志** - **全局过滤器(Global Filter)**:在MVC中,可以创建一个全局过滤器,该过滤器会在每个控制器的Action执行前后...

    SAP系统业务-查询系统日志信息.docx

    双击用户,可以深入查看该用户执行过的事务代码,这对于诊断性能问题、跟踪用户行为以及审计目的都非常实用。 3. **SE16N事务代码**: SE16N是一个强大的数据库表浏览器,允许用户直接查询和分析SAP数据库中的表。...

    Solaris日志记录介绍.doc

    通过sulog文件,管理员可以了解用户的操作记录,从而发现可疑的用户行为。 在sulog文件中,管理员需要密切关注两种用户,第一是反复su失败的用户,如cheny用户,他有猜超级用户口令的嫌疑。第二是在不正常时间的su...

    用户行为分析系统

    用户行为分析系统是一种用于收集、处理、理解和解读用户在特定平台上的交互行为的软件系统。在本案例中,系统是用Java编程语言开发的,并且是针对淘宝电商平台设计的。淘宝是中国最大的在线购物网站之一,拥有海量...

    log组件_labview日志组件_LOGO日志记录_

    LabVIEW(Laboratory Virtual Instrument Engineering Workbench)是一款强大的图形化编程环境,被广泛应用于测试、测量和控制...通过学习和掌握日志记录技术,开发者能够提升问题解决效率,确保软件系统的稳定运行。

    查看登录oracle数据库用户记录.docx

    要查看登录 Oracle 数据库的用户记录,可以创建一个临时日志表,并建立一个数据库表触发器来记录用户的登录行为。 创建临时日志表 首先,需要创建一个临时日志表来存储用户的登录记录。可以使用以下 SQL 语句来...

    LabVIEW日志记录实时更新模块.zip

    在日志记录模块中,main.vi可能包含了初始化日志系统、调用updateInfo.vi来实时更新日志、以及可能的错误处理和关闭日志等操作。用户界面(UI)设计可能会有一个实时更新的显示区域,用于查看正在记录的数据,同时...

    SAP用户登录日志(电脑名-IP地址-MAC地址-电脑用户名)

    * 用户行为记录和追踪 * 系统安全性和合规性检查 * 用户登录日志查询和分析 结论 SAP用户登录日志系统是一个功能强大和安全的工具,能够记录和追踪用户在SAP系统中的登录行为。该系统的设计和实现基于SAP ABAP编程...

    通用用户权限系统日志管理功能

    通过这些记录,管理员能够追踪和分析用户行为,及时发现并解决系统使用过程中的问题,比如错误操作或潜在的安全威胁。 UPMS通用用户权限系统将日志管理分为用户操作日志管理和用户登录日志管理两大部分。操作日志...

    局域网共享文件访问日志记录软件、控制共享文件访问操作软件的使用

    由于Windows操作系统自带的日志记录功能有限,这种专门的第三方软件为管理员提供了更全面的控制和监控手段,是企业网络管理的有效补充。 总之,大势至共享文件访问控制软件为企业提供了一种强大的工具,用于管理和...

    基于标签的用户行为日志大数据分析系统

    【作品名称】:基于标签的用户行为日志大数据分析系统 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】: 实时etl: mock...

    Unix系统用户登录、操作命令日志配置方法-(一).doc

    Unix系统用户登录和操作命令日志配置方法 一、简介 在 Unix 系统中,日志配置是非常重要的,它可以记录用户的登录和操作信息,从而提供系统安全和故障诊断的依据。本文将详细介绍 Unix 系统用户登录和操作命令日志...

    JSPSmart系统-权限管理与日志记录模块的设计与开发(源代码+论文).zip

    日志记录模块是系统监控的重要工具,它能记录用户的操作行为、系统运行状态以及异常信息。在JSPSmart系统中,日志记录包括以下方面: 1. 操作日志:记录用户在系统中的所有操作,如登录、退出、修改信息等,以便于...

Global site tag (gtag.js) - Google Analytics