知ってた? log4jのLoggerとCategoryの違い。

log4jを検索して情報を集めていると、設定ファイルに「log4j.logger.xxx」となっていたり、「log4j.category.xxx」となっていたりして、どっちが正しいのか混乱したので、まとめてみました。

前提

  • 検証したのは、log4j-1.2.17.jar

結論としては

  • 設定ファイルに記述は、loggerとcategoryどっちで書いても同じ。
  • Javaの記述は、Loggerを使う。

以降、ソースを抜粋しながら検証します。

設定ファイルに記述は、loggerとcategoryどっちで書いても同じ。

log4j.properties

# どっちでも同じ
log4j.rootLogger=INFO, A
log4j.logger.xxx=INFO, B

log4j.rootCategory=INFO, A
log4j.category.xxx=INFO, B

org.apache.log4j.PropertyConfigrator.java (Line:102)

  static final String CATEGORY_PREFIX = "log4j.category.";
  static final String LOGGER_PREFIX = "log4j.logger.";

org.apache.log4j.PropertyConfigrator.java (Line:654)

  /**
     Parse non-root elements, such non-root categories and renderers.
  */
  protected
  void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
    Enumeration enumeration = props.propertyNames();
    while(enumeration.hasMoreElements()) {
      String key = (String) enumeration.nextElement();
      if(key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
 String loggerName = null;
 if(key.startsWith(CATEGORY_PREFIX)) {
   loggerName = key.substring(CATEGORY_PREFIX.length());
 } else if(key.startsWith(LOGGER_PREFIX)) {
   loggerName = key.substring(LOGGER_PREFIX.length());
 }
 String value = OptionConverter.findAndSubst(key, props);
 Logger logger = hierarchy.getLogger(loggerName, loggerFactory);  // loggerでもcategoryでもLoggerを生成している。

Javaの記述は、Loggerを使う。

Loggerクラスは、Categoryクラスを継承している。

org.apache.log4j.Logger.java

/**
  This is the central class in the log4j package. Most logging
  operations, except configuration, are done through this class.

  @since log4j 1.2

  @author Ceki Gülcü */
public class Logger extends Category {

「@since log4j 1.2」とあるので、1.1系ではCategoryクラスを利用していたと推測できます。なので、Categoryクラスで説明されているサイトは1.1系を前提に説明しているのだと思います。

CategoryクラスのgetInstance("xx")は非推奨メソッド

org.apache.log4j.Category.java (Line:516)

 /**
  * @deprecated Make sure to use {@link Logger#getLogger(String)} instead.
  */
  public
  static
  Category getInstance(String name) {
    return LogManager.getLogger(name);
  }

 /**
  * @deprecated Please make sure to use {@link Logger#getLogger(Class)} instead.
  */ 
  public
  static
  Category getInstance(Class clazz) {
    return LogManager.getLogger(clazz);
  }

コメントに Logger#getLoggerを使ってくれって書いてます。

Loggerは、ログレベルTRACEが利用可能

org.apache.log4j.Logger.java (Line:158)

    /**
     * Log a message object with the {@link org.apache.log4j.Level#TRACE TRACE} level.
     *
     * @param message the message object to log.
     * @see #debug(Object) for an explanation of the logic applied.
     * @since 1.2.12
     */
    public void trace(Object message) {
      if (repository.isDisabled(Level.TRACE_INT)) {
        return;
      }

      if (Level.TRACE.isGreaterOrEqual(this.getEffectiveLevel())) {
        forcedLog(FQCN, Level.TRACE, message, null);
      }
    }

    /**
     * Log a message object with the <code>TRACE</code> level including the
     * stack trace of the {@link Throwable}<code>t</code> passed as parameter.
     *
     * <p>
     * See {@link #debug(Object)} form for more detailed information.
     * </p>
     *
     * @param message the message object to log.
     * @param t the exception to log, including its stack trace.
     * @since 1.2.12
     */
    public void trace(Object message, Throwable t) {
      if (repository.isDisabled(Level.TRACE_INT)) {
        return;
      }

      if (Level.TRACE.isGreaterOrEqual(this.getEffectiveLevel())) {
        forcedLog(FQCN, Level.TRACE, message, t);
      }
    }

このメソッドは、Loggerで実装されているのでCategoryクラスでは利用できません。また、「@since 1.2.12」とあるので1.2.11以前は、traceはなかったのかな。きっと。

まとめ

log4j 1.2.17を見る限りでは、下位互換のためCategoryクラスを継承したLoggerクラスを準備し、Categoryクラスを非推奨にしたと推測できます。これにより、1.1系で利用していた設定ファイルにある「log4j.category.xx」も問題なく動作することができます。

ここで混乱が生じます。
1.2系を利用し、設定ファイル内で「log4j.category.xx」を記述するよう説明されているサイトを参考にしているとします。
すると、Java側ではLoggerクラスのインスタンスを使うため、設定ファイルとLoggerクラスの関連が見えなくなります。さらに、Logger(Category)は階層構造を構築できるため、この階層構造=Categoryの関連性を見出し腑に落ちたように思うのですが、「log4j.logger.xx」の存在が宙に浮きます。

この混乱がないよう上で検証したつもりですので、参考にして下さい。

log4jでLoggerひとつでログレベルの異なる複数のログ出力を実行する方法

DEBUG以上は標準出力に出力し、ERROR以上はファイルに出力したい場合、Loggerの階層構造とadditivityや、Appenderの複数設定を駆使しても実現することができません。また、Java側のロジックで別々のLoggerを取得して、同じログを出力する方法もありますが冗長です。
これを実現するには、AppenderのThreshold属性を利用してログレベルのしきい値でフィルタリングする方法があります。

設定(log4j.properties)

#---------------------------------------------------------------------------------------
# Logger
#---------------------------------------------------------------------------------------
# Root
log4j.rootLogger=DEBUG, CONSOLE,MORE_THAN_ERROR

#---------------------------------------------------------------------------------------
# Appender
#---------------------------------------------------------------------------------------
# for Console
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d %-5p %c{1} - %m%n

# for File
log4j.appender.MORE_THAN_ERROR=org.apache.log4j.RollingFileAppender
log4j.appender.MORE_THAN_ERROR.Threshold=ERROR
log4j.appender.MORE_THAN_ERROR.File=error.log
log4j.appender.MORE_THAN_ERROR.Append=true
log4j.appender.MORE_THAN_ERROR.MaxFileSize=1MB
log4j.appender.MORE_THAN_ERROR.MaxBackupIndex=3
log4j.appender.MORE_THAN_ERROR.layout=org.apache.log4j.PatternLayout
log4j.appender.MORE_THAN_ERROR.layout.ConversionPattern=%d %-5p %c{1} - %m%n

appender.MORE_THAN_ERRORのThreshold属性に「ERROR」を設定しています。
これによりappender.MORE_THAN_ERRORは、ログレベルERROR以上のログ出力しか行いません。では、例として以下のようなコードを実行してみます。

log4jのログ出力処理

import org.apache.log4j.Logger;

public class Main {

 public static void main(String[] args) {

  // rootLogger取得
  Logger rootLogger = Logger.getLogger("root");
  rootLogger.fatal("fatal");
  rootLogger.error("error");
  rootLogger.warn("warn");
  rootLogger.info("info");
  rootLogger.debug("debug");
  rootLogger.trace("trace");
 }
}

ひとつのLoggerに対して各ログレベルの出力を行なっています。結果は、次のとおりです。

出力結果

標準出力(appender.CONSOLEによる出力)
2013-01-18 00:48:31,976 FATAL root - fatal
2013-01-18 00:48:31,979 ERROR root - error
2013-01-18 00:48:31,979 WARN root - warn
2013-01-18 00:48:31,979 INFO root - info
2013-01-18 00:48:31,979 DEBUG root - debug
error.log(appender.MORE_THAN_ERRORによる出力)
2013-01-18 00:48:31,976 FATAL root - fatal
2013-01-18 00:48:31,979 ERROR root - error

rootLoggerのログレベルは、DEBUGなのでappender.CONSOLEの出力は、FATALからDEBUGまでとなっています。
これに対して、appender.MORE_THAN_ERRORの出力は、FATALからERRORまでとなっており、Threshold属性によりフィルタリングされていることがわかります。

まとめ

前回のLoggerの階層構造とadditivityの仕組みと合わせて使うことで、きめ細かなログ出力を行うことができます。組合せにはパズル的な要素がありますが、ポイントを押さえて思い通りのログ出力を実現して下さい。

log4jのLoggerの階層構造とadditivityの振舞について

Loggerの階層構造について

Loggerは階層構造を作ることができます。例えば、

#---------------------------------------------------------------------------------------
# Logger
#---------------------------------------------------------------------------------------
# rootLogger
log4j.rootLogger=DEBUG, ROOT

# logger.x
log4j.logger.x=DEBUG, ROOT

# logger.x.y
log4j.logger.x.y=DEBUG, ROOT

# logger.x.y.z
log4j.logger.x.y.z=DEBUG, ROOT

とした場合、rootLoggerを始祖として、
[rootLogger] -> [logger.x] -> [logger.x.y] -> [logger.x.y.z] という親子関係が構築されます。
この親子関係には、子Loggerのログ出力が親Loggerに伝搬する仕様があります。これによって、[logger.x.y.z]に、ログレベルDEBUG以上の出力を行うと、そのログ出力は祖先に伝搬し[Appender.ROOT]により、4回のログ出力が実行されることになります。

伝搬を止める属性 - additivity

親Loggerへの伝搬を止めるためには、additivityという属性にfalseを設定します。(初期値ははtrue)

#---------------------------------------------------------------------------------------
# Logger
#---------------------------------------------------------------------------------------
# rootLogger
log4j.rootLogger=DEBUG, ROOT

# logger.x
log4j.logger.x=DEBUG, ROOT
log4j.additivity.x=false

# logger.x.y
log4j.logger.x.y=DEBUG, ROOT
log4j.additivity.x.y=false

# logger.x.y.z
log4j.logger.x.y.z=DEBUG, ROOT
log4j.additivity.x.y.z=false

このようにした場合、
[logger.x.y.z]に、ログレベルDEBUG以上の出力を行うと、そのログは祖先に伝搬せず1回の出力に留まります。

additivityの仕様

では、中間の[logger.x.y]のみadditivityをfalseとして、[logger.x.y.x]に、ログレベルDEBUG以上の出力を行うとログの出力は何回になるでしょう。

#---------------------------------------------------------------------------------------
# Logger
#---------------------------------------------------------------------------------------
# rootLogger
log4j.rootLogger=DEBUG, ROOT

# logger.x
log4j.logger.x=DEBUG, ROOT
log4j.additivity.x=true

# logger.x.y
log4j.logger.x.y=DEBUG, ROOT
log4j.additivity.x.y=false

# logger.x.y.z
log4j.logger.x.y.z=DEBUG, ROOT
log4j.additivity.x.y.z=true

このようにした場合、ログの出力は2回となります。
これは、additivityの判定仕様が以下のようになっているためです。

  1. 指定されたLoggerのログ出力
  2. 指定されたLoggerのadditivityの判定
    1. trueの場合、親Loggerに処理を委譲(1.に戻る)
    2. falseの場合、処理を終了

このため、中間のLoggerでadditivityをfalseとした場合は、falseとしたLoggerではログ出力され、それより祖先のLoggerにログ出力が伝搬しないので注意が必要です。

ログレベルは、起点となるLoggerの設定が適用される

Loggerに設定されているログレベルについて検証します。
additivity初期値trueを適用しログ出力が伝搬する設定としていますが、[logger.x.y.x]より祖先のLoggerに、ログレベルINFOを設定しています。
[logger.x.y.x]に、ログレベルDEBUGの出力を行うとログの出力は何回になるでしょう。

#---------------------------------------------------------------------------------------
# Logger
#---------------------------------------------------------------------------------------
# rootLogger
log4j.rootLogger=INFO, ROOT

# logger.x
log4j.logger.x=INFO, ROOT

# logger.x.y
log4j.logger.x.y=INFO, ROOT

# logger.x.y.z
log4j.logger.x.y.z=DEBUG, ROOT

このようにした場合、ログの出力は4回となります。
これは、ログ出力のログレベル判定には、起点(この例の場合、[logger.x.y.x])のログレベルDEBUGが適用されるためです。

このため、rootLoggerのログレベルをTRACEまで下げたとしても、子Loggerのログレベルが適用されるため伝搬してログ出力されるので注意が必要です。

まとめ

  • additivityとfalseとした場合、それより祖先のLoggerには伝搬しない。(falseを設定したLoggerの出力は実行される)
  • 出力されるログレベルは、起点となるLoggerのログレベルが適用される。

log4jの設定ファイル(log4j.properties)の書き方

アプリケーションは、リリースした段階で終わりではありません。リリース後も以下のようなアプリケーションログを出力し、システムの安定稼働を継続するためのメンテナンスや、業務改善のフィードバックを行うことも大事な機能のひとつです。

  • 利用状況の把握
  • 不具合発生時の状況の把握
  • レポーティングに利用する情報収集

様々だけど、運用に入ってから泣きを見ないように、しっかり設定しておきたい。

log4jを使ってログ出力

log4jは、Apache logging serviceのひとつで、Javaのロギングユーティリティです。今回は、Apache log4j 1.2.17について記載します。

  • 柔軟な出力形式が可能
    • テキスト形式の出力はもちろん、HTML、XMLのファイル出力、メール送信なんてのもある。
  • 6段階のログレベルを提供
    • ログの重要度に対してレベル付けすることができ、設定ファイルのパラメータでログ出力するレベルを簡単に変更できる。
  • 外部の設定ファイル
    • ライブラリから分離した設定ファイルとして、プロパティファイル(log4j.properties)、またはXMLファイルで設定することが可能です。

これによって、リリース向けビルド時に、環境に合わせた設定ファイルに差し替えるだけでログ出力をコントロールできます。

設定ファイルの構成

Logger
  • 名前
    • Loggerに名前を付けます。ピリオドで区切ることで階層構成(親子関係)を作ることができます。デフォルト設定では、子Loggerのログは親Loggerに伝搬します。
  • ログレベル
    • 6段階のログレベル(FATAL>ERROR>WARN>INFO>DEBUG>TRACE)の中から設定します。例えば、INFOを設定するとFATAL,ERROR,WARN,INFOレベルのログが出力され、DEBUG,TRACEレベルのログは出力されません。SAStrutsで開発時にrootLoggerのログレベルがDEBUGに設定されていて、session,request,context等の情報が氾濫し見づらい時は、ログレベルをINFOにしておくと重要なログを見逃すことがなく自分好みです。必要な時だけDEBUGに戻します。
  • Appender
    • カンマ区切りで複数指定できます。複数指定できるのでログを、コンソールとファイルに同時に出力することが可能です。これについては、後述します。
Appener

出力形式の設定をします。コンソール、ファイル、Syslogやメール送信などがあり多彩な出力が可能です。

  • 名前
    • Appenderに名前を付けます。この名前とLoggerのAppender設定が関連づきます。
  • Layout(+各種オプション)
    • Layoutクラスの指定と、各Layoutクラスのオプションを設定します。これについては、後述します。
Layout

Appenderに対して出力レイアウトを設定します。
Layout一覧

サンプル設定ファイル(log4j.properties)

さて、やっと本題。

#---------------------------------------------------------------------------------------
# Logger
#---------------------------------------------------------------------------------------
# Root
log4j.rootLogger=INFO, ROOT

# OperationLog
log4j.logger.operation=DEBUG, OPERATION

#---------------------------------------------------------------------------------------
# Appender
#---------------------------------------------------------------------------------------
# for Root
log4j.appender.ROOT=org.apache.log4j.ConsoleAppender
log4j.appender.ROOT.layout=org.apache.log4j.PatternLayout
log4j.appender.ROOT.layout.ConversionPattern=%-5p - %m%n

# for SQL
log4j.appender.OPERATION=org.apache.log4j.RollingFileAppender
log4j.appender.OPERATION.File=/var/log/tomcat/operation.log
log4j.appender.OPERATION.Append=true
log4j.appender.OPERATION.MaxFileSize=1MB
log4j.appender.OPERATION.MaxBackupIndex=20
log4j.appender.OPERATION.layout=org.apache.log4j.PatternLayout
log4j.appender.OPERATION.layout.ConversionPattern=%-5p - %m%n

出力処理

Logger logger = Logger.getLogger("operation");
logger.fatal("test operation fatal");
logger.error("test operation error");
logger.warn("test operation warn");
logger.info("test operation info");
logger.debug("test operation debug");
logger.trace("test operation trace");

出力結果

まず、Logger(operation)により、/var/log/tomcat/operation.logにログレベルDEBUG以上のログが出力されます。

/var/log/tomcat/operation.log
FATAL - test operation fatal
ERROR - test operation error
WARN  - test operation warn
INFO  - test operation info
DEBUG - test operation debug


次に、rootLoggerにより、コンソールにログレベルINFO以上のログが出力されます。これは、Logger(operation)が、rootLoggerの子Loggerになっているためで、Logger(operation)のログ出力がrootLoggerに伝搬しコンソールに出力されます。
この伝搬の仕組みについては、長くなるので、次のエントリーに回します。

コンソール
FATAL - test operation fatal
ERROR - test operation error
WARN  - test operation warn
INFO  - test operation info

JasperReportsでパラメータとフィールドを使ってPDFを出力する

前回は、PDFのデザインとプログラムからの出力できるところまでやったので、今回は、変数をバインドしてPDFを作成します。
ですので、変数のバインド先をiReportで設定することと、プログラムで変数を引き渡すところが今回の趣旨になります。

パラメータの作成

Report InspectorのParametersから「追加parameter」を選択します。
https://lh4.googleusercontent.com/-A3I0R57hYy4/UN2CjiAk7qI/AAAAAAAAOAc/m0e5PiHAiVk/s800/01_iReport.jpg


パラメータのプロパティは以下のようになります。今回はブログタイトルを表示しようと思いますので、パラメータ名を[blog_title]としました。
https://lh4.googleusercontent.com/-vzLSueOxvWQ/UN2CaOySaGI/AAAAAAAAN_Y/lfmMj3rJ6DQ/s800/02_Jaspersoft%2520iReport%2520Designer%25205.0.0.jpg

name パラメータ名
Parameter Class パラメータの型
Use as a prompt チェックすることで、Preview時にパラメータの表示値を入力するダイアログが表示
Default Value Expression 初期値を設定
Description パラメータの説明文
Properties SQLを利用する場合の入力値を設定

パラメータの配置

パレットからText Fieldを配置して、プロパティのText Field Expressionの[…]を押下します。するとダイアログが表示され、バインドするパラメータを編集することができます。
https://lh3.googleusercontent.com/-ousWucgmk8g/UN2CbFtxEJI/AAAAAAAAN_g/rsFNj7EmacA/s640/03_%2524F%257Bfield%257D%2520-%2520Text%2520Field%2520Expression.jpg

フィールドの作成

フィールドは繰り返し表示するリストの項目を設定します。今回は、「日付」と「タイトル」を列とするリストを作成しました。
https://lh3.googleusercontent.com/-sI4LbKmhMqk/UN2Cbv8_PcI/AAAAAAAAN_o/BxhGujwjtkA/s640/04_Jaspersoft%2520iReport%2520Designer%25205.0.0-2.jpg

Beanを作成

リストのデータを引き渡すためのBeanを作成します。

public class RowBean {

 /** 日付 */
 private String date;
 /** タイトル */
 private String title;

 /* getter/setterは省略 */
}
クラスパスを設定

[iReport]-[環境設定]でオプションダイアログを起動します。[Classpath]タグから[Add Folder]を押下して、プロジェクトフォルダを追加します。
https://lh4.googleusercontent.com/-aksRSGTwI-I/UN2Cc39jWEI/AAAAAAAAN_4/oqnbKvmIR_I/s640/05_%25E3%2582%25AA%25E3%2583%2597%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%25B3.jpg

Report Queryを起動

デザインビューの上部にあるアイコンを押下します。
https://lh5.googleusercontent.com/-bW9IjTie2Ac/UN2CcVZOvGI/AAAAAAAAN_w/9GcGICMWGq0/s800/06_Jaspersoft%2520iReport%2520Designer%25205.0.0-3.jpg

Beanからフィールドを読み込む

Beanを読み込んでクラスメンバを表示して、フィールドに定義するクラスメンバを選択して[Add Selected Field(s)]を押下します。
https://lh3.googleusercontent.com/-9PJGh6WUef8/UN2CdoJrubI/AAAAAAAAOAA/ZRGXEJkIkDQ/s800/07_Report%2520query.jpg

これで、フィールドが追加されました。
https://lh5.googleusercontent.com/-p3A6zZ9VpOw/UN2CeHITeyI/AAAAAAAAOAI/rNdWWRYxWOY/s800/08_Jaspersoft%2520iReport%2520Designer%25205.0.0-4.jpg

フィールドの配置

パラメータの時と同様に、フィールドを配置します。
https://lh5.googleusercontent.com/-ECassqAu_ys/UN2Ceq-NDUI/AAAAAAAAOAQ/ECoil1zu_Po/s800/09_Jaspersoft%2520iReport%2520Designer%25205.0.0-5.jpg

PDF出力

ソースはこちら。

 @Execute(validator = false)
 public String param() throws JRException, IOException {

  String path = RequestUtil.getRequest().getSession().getServletContext()
    .getRealPath("/WEB-INF/templates/parameter_report.jasper");

  /* パラメータにバインドするデータ */
  Map<String, Object> param = new HashMap<String, Object>();
  param.put("blog_title", "DISってHONEY");
  
  /* フィールドにバインドするデータ */
  List<RowBean> fields = new ArrayList<RowBean>();
  RowBean rowBean1 = new RowBean();
  rowBean1.setDate("2012-12-25");
  rowBean1.setTitle("JasperReportを利用してSAStrutsでPDF出力するサンプル");
  fields.add(rowBean1);
  
  RowBean rowBean2 = new RowBean();
  rowBean2.setDate("2012-12-20");
  rowBean2.setTitle("PDF帳票作成にはこれ!JasperReportsで日本語(IPAexフォント)を使うまで");
  fields.add(rowBean2);
  
  byte[] bytes = JasperRunManager.runReportToPdf(path,
    param, new JRBeanCollectionDataSource(fields));

  HttpServletResponse response = ResponseUtil.getResponse();
  response.setContentType("application/pdf");
  response.setContentLength(bytes.length);
  OutputStream out = response.getOutputStream();
  out.write(bytes);
  out.flush();
  out.close();

  return null;
 }

ちょっとテキストが切れてますが、パラメータとフィールドを使ったPDF出力ができました。
https://lh3.googleusercontent.com/-gAryjT-L7e8/UN2CfA9xdSI/AAAAAAAAOAU/XOU1vx9_rfE/s640/10_param.jpg

まとめ

これで、作成できるPDF帳票の幅が広がりましたね(・∀・)

JasperReportsを利用してSAStrutsでPDF出力するサンプル

ダウンロード

以下のリンクからjasperreports-5.0.0-project.zipをダウンロードします。
Jasper Reports 5.0.0 ダウンロード

jasperreports-5.0.0-project.zip を解凍して、

  • /lib/commons-beanutils-1.8.0.jar
  • /lib/commons-collections-2.1.1.jar
  • /lib/commons-digester-2.1.jar
  • /lib/commons-logging-1.1.1.jar
  • /lib/iText-2.1.7.js1.jar
  • /dist/jasperreports-5.0.0.jar

をコピーしてビルドパスを通します。
apache commonsについては重複するのでバージョンの新しいものにビルドパスを通します。

テンプレートファイルを作成

WEB-INF/templatesフォルダを作成してテンプレートを作成します。iReportのインストールについては、PDF帳票作成にはこれ!JasperReportsで日本語(IPAexフォント)を使うまで - DISってHONEY♪ @gungnir_odinを参考にしてください。


テンプレートのプロパティで、LanguageにJavaを選択します。
https://lh4.googleusercontent.com/-8W9EyqTcq84/UNnDco_0CYI/AAAAAAAAN9Y/SghdVHVxPFM/s800/01_Jaspersoft%2520iReport%2520Designer%25205.0.0.jpg


簡単なテキストを追加しました。
https://lh6.googleusercontent.com/-j_-NJJWS9_k/UNnDZcgr1FI/AAAAAAAAN84/oUKq3LAJW_8/s640/02_Jaspersoft%2520iReport%2520Designer%25205.0.0.jpg

テンプレートファイルをコンパイル

テンプレートのメニューから、「Compile Report」を選択してテンプレートファイルをコンパイルします。
https://lh6.googleusercontent.com/-kmeebDaOHcI/UNnDaIoUc0I/AAAAAAAAN9E/qvn2mSwtEY8/s800/03_compile_report.jpg


コンパイルするとjasperファイルが生成されます。
https://lh4.googleusercontent.com/-imYrOIvZTc8/UNnDbNoGHsI/AAAAAAAAN9M/4m2U3kEUuCM/s800/04_jasper_file.jpg

PDF出力処理

Actionクラスに以下の処理を記述します。

 @Execute(validator = false)
 public String index() throws JRException, IOException {

  String path = RequestUtil.getRequest().getSession().getServletContext()
    .getRealPath("/WEB-INF/templates/simple_report.jasper");

  byte[] bytes = JasperRunManager.runReportToPdf(path,
    new HashMap<String, Object>(), new JREmptyDataSource());

  HttpServletResponse response = ResponseUtil.getResponse();
  response.setContentType("application/pdf");
  response.setContentLength(bytes.length);
  OutputStream out = response.getOutputStream();
  out.write(bytes);
  out.flush();
  out.close();

  return null;
 }

PDF出力結果

https://lh6.googleusercontent.com/-5_I-pJ_-LmQ/UNnDbsqq30I/AAAAAAAAN9Q/Om2bhhEWDlA/s640/05_plain.jpg

まとめ

これでひとまずPDF出力が可能になりました。Jasper Reportsは導入までの学習コストも低く、手軽に導入することができそうです。

PDF帳票作成にはこれ!JasperReportsで日本語(IPAexフォント)を使うまで

JasperReportsは、オープンソースの帳票作成ライブラリでPDFやExcelファイルを生成することができます。帳票レイアウトのデザインには、iReportというツールを提供しており、これもオープンソースで各OSに対応したアーカイブが提供されています。

iReportは、グラフィカルにデザインができ、すぐにプレビューできるためるため、帳票作成には欠かせないツールとなります。

そこで、日本語フォントとして定評があるIPAexフォントを利用して、iReportで日本語を含んだPDF帳票を作成するところまで紹介します。

IPAexフォントをiReportに設定

今回IPAexフォントを選んだのは、オープンソースライセンス(厳密には"IPAフォントライセンス")で提供されているためです。生成するPDFに使用しているフォントを埋め込む必要がある場合、OS固有のフォント(ex. MSゴシック、MS明朝 等)ではライセンス違反にあたる可能性があります。

さて、設定していきます。

IPAexフォントを解凍

ダウンロードしたIPAexfont00201.zipを解凍します。解凍すると以下のttfファイルが展開されます。

  • ipaexg.ttf (IPAexGothic)
  • ipaexm.ttf (IPAexMincho)
iReportの設定

MacOSXの場合
[iReport]-[環境設定]で[オプションダイアログ]を表示し、[iReport]-[Fonts]-[Install Font]を押下します。
https://lh3.googleusercontent.com/-9_Y8aVdrxvs/UNMAUm6V5wI/AAAAAAAAN0s/3MpVxljSPCY/s640/01_Font%2520Installation.jpg
ここで、ipaexg.ttfのパスを指定して「次へ >」を押下します。


https://lh5.googleusercontent.com/-UQ_YMQ0EV5o/UNMAS2UyWUI/AAAAAAAAN0c/HOyQ_iNKCFI/s640/02_Font%2520Installation%2520Family%2520Detail.jpg

PDF Details欄で、PDF Encodingに「Identity-H (Unicode with horizontal writing)」、Embed this font in the PDF documentにチェックを付けます。
IPAexフォントを使用したPDFを、IPAexフォントがインストールされていないOSで開いた場合、文字化けする可能性があるためフォントをPDFに埋め込む必要があります。その設定を行なっています。

ipaexm.ttfについても、同じように設定を行います。

サンプルレポートを作成

下のように、IPAexフォントを使用したレポートを作成しました。
https://lh6.googleusercontent.com/-yhvOH6UCCvs/UNMAVv9U4WI/AAAAAAAAN04/lr-T_wbNzW8/s640/03_Jaspersoft%2520iReport%2520Designer%25205.0.0-1.jpg

iReportのプレビューでIPAexフォントが表示されることを確認

正常にIPAexフォントで表示されていることが確認できました。

https://lh3.googleusercontent.com/--BameAagksU/UNMAVE3j6OI/AAAAAAAAN00/ESRxUA_vLh4/s640/04_Jaspersoft%2520iReport%2520Designer%25205.0.0.jpg

IPAexフォントがPDFに埋め込まれていることを確認

PDFを出力して、Adobe Readerで[ファイル]-[プロパティ]を選択し「文章のプロパティ」を表示します。フォントタブで、画像のようにIPAexフォントが埋め込みサブセットとして表示されています。これでOKです。
https://lh5.googleusercontent.com/-eYfwlSZig3U/UNMATuOLA8I/AAAAAAAAN0k/38K2jZEHa9Y/s640/05_confilm%2520pdf.jpg

まとめ

日本語を含んだPDF帳票を作成する場合、JasperReports+iReport+IPAフォントの組合せは、もう鉄板ではないでしょうか。
これで環境ができましたので、どんどん帳票デザインしてみましょう〜 (*´ω`*)ノシ