<はじめに>

どうも!
おかずは会心の出来なのに、よく米を炊き忘れるさとこ(♂)です。
 
前回はSwiftの標準出力関数(応用)について学び漁りましたが、
今回は「NSLog」、「os_log」についての記事になります。
また漁っていきますよーっ
 
○過去記事
Swift 3系、Xcode 8におけるログ出力関連機能を漁ってみた~その1~
Swift 3系、Xcode 8におけるログ出力関連機能を漁ってみた~その2~
 
○次回記事(2017/08/29更新)
Swift 3系、Xcode 8におけるログ出力関連機能を漁ってみた~その4~
 

<本記事の内容>

【今回の内容】
・NSLogとos_logのご紹介
 
【次回以降の内容】
・さとこの3分カスタマイズ
・Xcodeのデバッガでログ出力

テテテッテンテッテンテンなコーナーは次回に回します。
 

<NSLog、os_Log用の定数など>

前々回は画面キャプチャにしてましたが、コピペしてもらった方が実動作確認し易いのでコードで提示します。
今回もこちらを使用します。

let int: Int = 100
let string: String = "文字列"
let bool: Bool = true
let array: [Any] = [1, "one", true, self.view.frame]
let dictionary: [String : Any] = ["Int型":100, "String型":"辞書", "Bool型":false]
let optional: Optional = "オプショナル型"

protocol MyProtocol {
    func MyProtocol()
}

class MyClass {
    let intValue = 100
    let stringValue = "文字列"
    let boolValue = true
}

struct MyStruct {
    let intValue = 100
    let stringValue = "文字列"
    let boolValue = true
}

enum MyEnum {
    case enumcase1
    case enumcase2
}

func myFunctionRetunVoid() -> Void {}
func myFunctionRetunTupple() -> (int: Int, string: String, bool: Bool) { return (100, "文字列型", true) }
func myFunctionClosure(closure: @escaping (Bool) -> (Void)) { closure(true) }

<使い方 ~NSLog編~>

・NSLog
「String」を引数とするログ出力関数で、タイムスタンプにも対応してます。

// 定義
func NSLog(_ format: String, _ args: CVarArg...)

元々Objective-C由来で、公式ドキュメントとの定義が異なるので、Xcodeよりサーチしたものを載せてます。
(メニュー -> Search Documentation for Selected Textより。
Objective-Cはこちら

74fa6a16 d88e 4730 b471 9b26c0be69a2

【キーワード1】
・format
・args
 
【キーワード2】
・フォーマット指定子:String Format Specifiers
参考:String Format Specifiers
 
・CVarArg
→C言語由来の型で、可変個数の引数を扱う
参考:
公式ドキュメントより
C言語 可変引数リストの処理

元々引数の型がNSString型ということもあり、互換性のあるString以外の型はString型に変換するか、フォーマット指定子を使用したフォーマット変換、もしくは、文字列埋め込みが必要になりますよと。
 

【NSLog】

NSLog("\(int)")
NSLog("\(string)")
NSLog("\(bool)")
NSLog("\(optional ?? "デフォルト値はnilです。")")
NSLog("\(optional!)")
NSLog("\(optional as Any)")
NSLog("\(array)")
NSLog("\(dictionary)")
NSLog("\(self.view.frame)")
NSLog("\(MyClass())")
NSLog("\(MyStruct())")
NSLog("\(MyEnum.enumcase1)")
NSLog("\(MyProtocol.self)")
NSLog("\(int == 100)")
NSLog("\(myFunctionRetunVoid())")
NSLog("\(myFunctionRetunTupple())")
NSLog("\(myFunctionClosure(closure: { (isClosure) -> (Void) in NSLog("クロージャの結果 = \(isClosure)"); return }))")

【出力結果】

2017-08-13 00:25:07.304854+0900 SampleLogAppXcode8[5457:2348031] 100
2017-08-13 00:25:07.304882+0900 SampleLogAppXcode8[5457:2348031] 文字列
2017-08-13 00:25:07.304899+0900 SampleLogAppXcode8[5457:2348031] true
2017-08-13 00:25:07.304917+0900 SampleLogAppXcode8[5457:2348031] オプショナル型
2017-08-13 00:25:07.305031+0900 SampleLogAppXcode8[5457:2348031] オプショナル型
2017-08-13 00:25:07.305080+0900 SampleLogAppXcode8[5457:2348031] Optional("オプショナル型")
2017-08-13 00:25:07.305131+0900 SampleLogAppXcode8[5457:2348031] [1, "one", true, (0.0, 0.0, 414.0, 736.0)]
2017-08-13 00:25:07.305174+0900 SampleLogAppXcode8[5457:2348031] ["String型": "辞書", "Int型": 100, "Bool型": false]
2017-08-13 00:25:07.305213+0900 SampleLogAppXcode8[5457:2348031] (0.0, 0.0, 414.0, 736.0)
2017-08-13 00:25:07.305239+0900 SampleLogAppXcode8[5457:2348031] SampleLogAppXcode8.ViewController.MyClass
2017-08-13 00:25:07.305281+0900 SampleLogAppXcode8[5457:2348031] MyStruct(intValue: 100, stringValue: "文字列", boolValue: true) 
2017-08-13 00:25:07.305305+0900 SampleLogAppXcode8[5457:2348031] enumcase1
2017-08-13 00:25:07.305352+0900 SampleLogAppXcode8[5457:2348031] MyProtocol
2017-08-13 00:25:07.305415+0900 SampleLogAppXcode8[5457:2348031] true
2017-08-13 00:25:07.305439+0900 SampleLogAppXcode8[5457:2348031] ()
2017-08-13 00:25:07.305493+0900 SampleLogAppXcode8[5457:2348031] (int: 100, string: "文字列型", bool: true)
2017-08-13 00:25:07.305511+0900 SampleLogAppXcode8[5457:2348031] クロージャの結果 = true
2017-08-13 00:25:07.305531+0900 SampleLogAppXcode8[5457:2348031] ()

と、こんな感じです。
内容としては前回記事の【print】と同じですね。
 
やはりタイムスタンプがあると出力情報が肥えた感じがします。
(でも冗長感もあるのはプロジェクト名の所為かな。
 
早速遊んでみます!

// フォーマット指定子を使う場合
NSLog("%@", "次のログ出力は")
NSLog("%d%@", #line, "行目です。")

// 出力結果
2017-08-13 00:33:41.134 SampleLogAppXcode8[2946:6805532] 次のログ出力は
2017-08-13 00:33:41.134 SampleLogAppXcode8[2946:6805532] 264行目です。

お、予想どおり。
カスタマイズもできそう。
 
第二引数が可変引数を許容してるしこれも試そう。

// 引数が複数の場合
// string=文字列、int=100、bool=true
NSLog(string, "\(int)", "\(bool)")

// 出力結果
2017-08-13 00:35:34.106 SampleLogAppXcode8[3284:6815046] 文字列

( ゚д゚ )?
「int」と「bool」の値はどこいった??
(疲れてるのかな、自分。
 
…再度実行するも同様の結果に。
( ゚д゚ )?
 
…あ、そういうことか。
あくまでカンマ区切りはフォーマット指定子のためか。
複数出力の場合はフォーマット指定子使うか、もしくは埋め込みしろよと。
(さとこ、気づくの遅いぞ。
 

>>ちなみに...

引数無しだと「Cannot invoke 'NSLog' with no arguments.」でコンパイルエラーになります。
→「NSLog」の呼び出しは引数指定しろよと。

// エスケープシーケンスは出力される
NSLog("\t")
NSLog("\n")

// 出力結果
2017-08-13 00:35:34.106 SampleLogAppXcode8[3284:6815046]    
2017-08-13 00:35:34.106 SampleLogAppXcode8[3284:6815046] 

// ブランクは出力されない
NSLog("") // 出力結果なし

ただ上記みたいにブランクだとタイムスタンプやプロジェクト名も何も出力されません。
 
では、この辺でまとめです!
 

【NSLogにおける使用可能な引数の概要】

引数一覧 NSLogの機能 初期値 説明
_ format ログ出力 なし フォーマット指定子、引数の指定 *1
_ args ログ出力 なし 引数の指定 *2

*1. 最低1つの引数が必須となり、複数指定(カンマ区切り)が可能。尚、複数指定の場合、出力対象は第一引数となる。
*2. 指定する引数の型はNSString(String)、文字列埋め込み、または、フォーマット指定子の対象となる型であること。
 

【NSLog使用例】

指定引数 使用例:条件(var a = ["X":1, "Y":2, "Z":3]) 出力結果
_ format e.g.1 NSLog("\(a)") e.g.1)参照
_ format e.g.2 NSLog("%@", "\(String(describing: a.popFirst()))") e.g.2)参照
_ args e.g.3 NSLog("%d-%d-%d", a["X"] ?? 0, a["Y"] ?? 0, a["Z"] ?? 0) e.g.3)参照

 

【NSLog使用例(出力結果)】

// e.g.1)
2017-08-13 00:36:34.290 SampleLogAppXcode8[2064:29074] ["X": 1, "Z": 3, "Y": 2]

// e.g.2)
2017-08-13 00:36:34.291 SampleLogAppXcode8[2064:29074] Optional((key: "X", value: 1))  

// e.g.3)
2017-08-13 00:36:34.291 SampleLogAppXcode8[2064:29074] 0-2-3

さあ、続いて「os_log」に移っていきます!
 

<使い方 ~os_log編~>

・os_log
iOS10以降で使用出来るようになった割と新しいログ出力関数になります。

// 定義
func os_log(_ message: StaticString, dso: UnsafeRawPointer? = #dsohandle, log:OSLog = default, type: OSLogType = default, _ args: CVarArg...)

【キーワード1】
・message
・dso
・log
・type
・args
 
【キーワード2】
・StaticString:型
→「コンパイル時に認識できる」テキスト表現で設計された文字列型。
参考:公式ドキュメントより
 
・#dsohandle:
→UnsafeRawPointer型のシンボル
→UnsafeRawPointerは、型が未指定のデータにアクセスするための未加工のポインタ
参考:UnsafeRawPointerについて

【使い方】

// インポートする
import os.log
 
// OSLog のインスタンスを生成する
let osLog = OSLog(subsystem: "com.yusayusa.SampleLogAppXcode8", category: "OSLOG")
 
// os_log 関数を呼び出す
os_log("os_logログです", log: osLog, type: .default)

使い方はさほど難しくなさそうですね。
コードのコメントのままですがこんな感じ。

  • importする
  • インスタンス生成する
  • 適当な引数を指定して使う

ポイントは「OSLog」でのオプション設定と、typeのログレベルでしょうか。
早速使ってみます!

// ログ
2017-08-13 00:37:18.275434+0900 SampleLogAppXcode8[2705:183270] [OSLOG] os_logログです!

あら?debugって文言出ないんかい。
(ちょっと期待した。
まあカスタマイズして出力できないこともなさそう。
 

ここからは「OSLog」についてちょいと詳しめに見てみます。
 

>> Xcodeの「OSLog」の定義より丸々抜粋

@function os_log_create

@abstract
Creates a log object to be used with other log related functions.

@discussion
Creates a log object to be used with other log related functions.
The log object serves two purposes:
(1) tag related messages by subsystem and category name for easy filtering, and
(2) control logging system behavior for messages.
The log object may be NULL if logging is disabled on the system.

@param subsystem
The identifier of the given subsystem should be in reverse DNS form
(i.e., com.company.mysubsystem).

@param category
The category within the given subsystem that specifies the settings for the log object.

@result
Returns an os_log_t value to be passed to other os_log API calls.
This should be called once at log initialization and rely on system
to detect changes to settings.
This object should be released when no longer used via os_release or -[release] method.

A value will always be returned to allow for dynamic enablement.

ふむふむ。
ざっくり言うとこんな感じ。
 
@abstract
→os_log使用時にオプション機能を付けられる
 
@discussion
→オプションには2つの目的がある

  • 出力結果のフィルタリング性向上
  • 出力制御

@param subsystem
→固有の識別子を持たせられる
 
@param category
→カテゴリ名を設定できる
 
@result
→初期化時に一度だけ呼ばれる、os_log特有の機能を制御オブジェクトを返す
→設定変更を検知するため動的に使用できる
→不要な場合は設定変更でオブジェクトを開放できる
 
なるほど。
os_logの利便性を向上させることができますよと。
でもあえてインスタンス生成しなくても使用できるみたいですね。
 
さっそく制御してみます。
 

【出力制御】

// disabledを設定してあげることで以降のログ出力は行われなくなる
osLog = OSLog.disabled
 
// os_log 関数を呼び出す
os_log("os_logログです", log: osLog, type: .default)

// 出力結果
// 何も出力されない

disabledを設定してあげるだけ。
単純明快ですね。
 
さてさて、
どばっと出力してみましょう!

// Int = 100
os_log("\(Int)")  // Cannot convert value of type 'String' to expected argument type 'StaticString'

と、
コンパイル時点でエラー発生。
出鼻をくじかれました。
 
エラー内容:
Stringは引数として予定されてるStaticStringに変換できぬからダメ!
 
読んで字のごとくですね。
 
あ、そういえば!と思いキーワードに戻ってみる。

「コンパイル時に認識できる」テキスト表現で設計された文字列型。

ふむ。
とりあえず思いついたので試す。

// StaticString()
os_log(StaticString(int))  // Argument labels '(_:)' do not match any available overloads

エラー内容:
→そんな初期化の仕方には対応しておらぬ!
 
オーケー。
じゃあこれや!

// StaticStringで宣言
let sInt: StaticString = "\(int)"
os_log(sInt) // Cannot convert value of type 'String' to specified type ’StaticString'.

エラー内容:
Stringは指定されたStaticStringに変換できぬからダメ!
 
かくなる上は…

// フォーマットじゃああああ!
os_log("%@", String(int)) // 問題なし!

キタ━━━━(゚∀゚)━━━━!!

取り乱しました…。
 

>>ちなみに...

// もちろん、こんな感じでもOK!
os_log("%@", String(reflecting: int))
os_log("%@", String(describing: int))
os_log("%@", "\(int)")
os_log("%d", int)

// 上記の出力結果(全部同じ)
2017-08-13 00:45:39.841321+0900 SampleLogAppXcode8[1244:771682] 100

NSLogについて前述してなかったらたどり着くのにもっと手間取った気がする...

では改めて!

os_log("%@", "\(int)")
os_log("%@", "\(string)")
os_log("%@", "\(bool)")
os_log("%@", "\(optional ?? "デフォルト値はnilです。")")
os_log("%@", "\(optional!)")
os_log("%@", "\(optional as Any)")
os_log("%@", "\(array)")
os_log("%@", "\(dictionary)")
os_log("%@", "\(self.view.frame)")
os_log("%@", "\(MyClass())")
os_log("%@", "\(MyStruct())")
os_log("%@", "\(MyEnum.enumcase1)")
os_log("%@", "\(MyProtocol.self)")
os_log("%@", "\(int == 100)")
os_log("%@", "\(myFunctionRetunVoid())")
os_log("%@", "\(myFunctionRetunTupple())")
os_log("%@", "\(myFunctionClosure(closure: { (isClosure) -> (Void) in os_log("%@", "クロージャの結果 = \(isClosure)"); return }))")

【出力結果】

2017-08-13 00:50:07.541642+0900 SampleLogAppXcode8[4274:563265] 100
2017-08-13 00:50:07.541861+0900 SampleLogAppXcode8[4274:563265] 文字列
2017-08-13 00:50:07.542132+0900 SampleLogAppXcode8[4274:563265] true
2017-08-13 00:50:07.542340+0900 SampleLogAppXcode8[4274:563265] オプショナル型
2017-08-13 00:50:07.542511+0900 SampleLogAppXcode8[4274:563265] オプショナル型
2017-08-13 00:50:07.542715+0900 SampleLogAppXcode8[4274:563265] Optional("オプショナル型")
2017-08-13 00:50:07.542999+0900 SampleLogAppXcode8[4274:563265] [1, "one", true, (0.0, 0.0, 414.0, 736.0)]
2017-08-13 00:50:07.543297+0900 SampleLogAppXcode8[4274:563265] ["String型": "辞書", "Int型": 100, "Bool型": false]
2017-08-13 00:50:07.543649+0900 SampleLogAppXcode8[4274:563265] (0.0, 0.0, 414.0, 736.0)
2017-08-13 00:50:07.543971+0900 SampleLogAppXcode8[4274:563265] SampleLogAppXcode8.ViewController.MyClass
2017-08-13 00:50:07.544228+0900 SampleLogAppXcode8[4274:563265] MyStruct(intValue: 100, stringValue: "文字列", boolValue: true)
2017-08-13 00:50:07.544431+0900 SampleLogAppXcode8[4274:563265] enumcase1
2017-08-13 00:50:07.544628+0900 SampleLogAppXcode8[4274:563265] MyProtocol
2017-08-13 00:50:07.544820+0900 SampleLogAppXcode8[4274:563265] true
2017-08-13 00:50:07.545010+0900 SampleLogAppXcode8[4274:563265] ()
2017-08-13 00:50:07.545203+0900 SampleLogAppXcode8[4274:563265] (int: 100, string: "文字列型", bool: true)
2017-08-13 00:50:07.545511+0900 SampleLogAppXcode8[4274:563265] クロージャの結果 = true
2017-08-13 00:50:07.545800+0900 SampleLogAppXcode8[4274:563265] ()

うん、「NSLog」と大差無いようです。
インスタンス生成してカテゴリ指定でその情報が増えるくらいですね。
 
ではでは、
触れまいと思っていた#dsohandleに移ります。
これに関しては文献が少なかったです。
(英語の記事でもコレどうやって使うん?みたいなの多かった。
 
見て見ぬ振りをしたいところですが、
それっぽい記事があったので載せておきます。
参考1:「Modernizing Swift's Debugging Identifiers
参考2:「Re: Accessing the bundle of the call-site
(丸投げ。
 
独自解釈で、この上なくざっくり言うと、

・参考1:
自身を指すポインタで、共有ライブラリ等との参照を確保するために使用できる命名標準

うん、言葉の意味は理解できるかな。

参考2:※一部抜粋
On Darwin #dsohandle points to a Mach-O file header.
→Darwinにおいては#dsohandleはMach-Oファイルのヘッダを指す。
参考:
Darwin (オペレーティングシステム)
Mach-O

んー、目的地に到達していない感が否めませんね。
ただ、こいつの型となるUnsafeRawPointerてのを調べると割と情報はあるのでSwiftDoc.orgの内容を載せておきます。
(「UnsafeRawPointerについて」
(公式ドキュメントと大差ありませんが、検索が早くてオススメです!
 
#dsohandleにおいては最初に提示した関数の定義のとおり、デフォル値が設定されていますが、まずは出力してみます!
申し訳程度に

// 今まで扱ったログ出力関数で出力してみる
print(#dsohandle)
debugPrint(#dsohandle)
dump(#dsohandle)
NSLog("\(#dsohandle)")
os_log("%@", "\(#dsohandle)")

// 出力結果
0x000000010b041000
0x000000010b041000
▿ 0x000000010b041000
  - pointerValue: 4479782912
2017-08-13 01:04:57.443 SampleLogAppXcode8[2106:2983148] 0x000000010b041000
2017-08-13 01:04:57.443863+0900 SampleLogAppXcode8[2106:2983148] 0x000000010b041000

 
出力値がどれも同じで、16進数表記です。
ただ一点異なる箇所と言えば、「dump」の出力内容ですかね。
ご丁寧に10進数表記pointerValueも出力してくれてます。
自身を指すポインタってのも値のデカさからなんとなく覗えますね。
2進数、8進数、10進数、16進数相互変換ツールにて確認済み。
 
UnsafeRawPointerは次回以降で紹介しようと思いますので、今回はこのあたりでご勘弁を。
 
それでは、まとめになります。
 

【os_logにおける使用可能な引数の概要】

引数一覧 os_logの機能 初期値 説明
_ message ログ出力 なし フォーマット指定子、引数の指定 *1
dso ログ出力 #dsohandle(:UnsafeRawPointer?) 自身を指すポインタの設定
log ログ出力 default(:OSLog) 出力制御の設定 *2
type ログ出力 default(:OSLogType) ログレベルの設定 *3
_ args ログ出力 なし 引数の指定 *4

*1. 最低1つの引数が必須となり、複数指定(カンマ区切り)が可能。指定時はStaticString型の文字列、または、文字列を直接指定する。
*2. 指定することにより出力制御が可能。(未指定も可)
*3. 指定することによりログレベルの設定が可能。(未指定も可)
*4. 指定する引数の型はNSString(String)、文字列埋め込み、または、フォーマット指定子の対象となる型であること。
 

【os_log使用例】

指定引数 使用例:条件(var a = ["X":1, "Y":2, "Z":3], var dso = 0, let osLog = OSLog(subsystem: "com.Xxxx.Yyyy", category: "OSLOG")) 出力結果
_ message os_log("TEST") e.g.)_ message参照
_ dso e.g.1 os_log("%@", dso: &dso, "\(a)") e.g.1)_ dso参照
_ dso e.g.2 os_log("%@", dso: #dsohandle, "\(a["X"]!)") e.g.2)_ dso参照
log e.g.1 os_log("%@", log: osLog, "\(a["Y"]!)") e.g.1)log参照
log e.g.2 os_log("TEST", log: OSLog.disabled) e.g.2)log参照
type os_log("%@", type: .debug, "\(a["Z"]!)" e.g.)type参照
_ args e.g.1 os_log("%@", dso: &dso, log: osLog, type: .debug, "\(String(describing: a.popFirst()))") e.g.1)_ args参照
_ args e.g.2 os_log("%d-%d-%d", dso: &dso, log: osLog, type: .debug, a["X"] ?? 0, a["Y"] ?? 0, a["Z"] ?? 0) e.g.2)_ args参照

 

【os_log使用例(出力結果)】

// e.g.)_ message
2017-08-14 22:03:13.550449+0900 SampleLogAppXcode8[4008:467956] TEST

// e.g.1)_ dso
2017-08-14 22:03:13.550707+0900 SampleLogAppXcode8[4008:467956] ["X": 1, "Z": 3, "Y": 2]

// e.g.2)_ dso
2017-08-14 22:03:13.550908+0900 SampleLogAppXcode8[4008:467956] 1

// e.g.1)log
2017-08-14 22:03:13.551138+0900 SampleLogAppXcode8[4008:467956] [OSLOG] 2

// e.g.2)log

// e.g.)type
2017-08-14 22:03:13.551357+0900 SampleLogAppXcode8[4008:467956] 3

// e.g.1)_ args
2017-08-14 22:03:13.551637+0900 SampleLogAppXcode8[4008:467956] [OSLOG] Optional((key: "X", value: 1))  

// e.g.2)_ args
2017-08-14 22:03:13.551885+0900 SampleLogAppXcode8[4008:467956] [OSLOG] 0-2-3

 
最後に、
前回記事で触れたエスケープシーケンスの例を遊びがてら出力してみます。

// オマケ
print("\"")
debugPrint("\"")
dump("\"")
NSLog("\"")
let osLog = OSLog(subsystem: "com.yusayusa.SampleLogAppXcode8", category: "OSLOG")
os_log("\"", log: osLog, type: .default)

// 出力結果
"
"\""
- "\""
2017-08-13 00:29:25.170641+0900 SampleLogAppXcode8[5467:2352369] "
2017-08-13 00:29:25.170743+0900 SampleLogAppXcode8[5467:2352369] [OSLOG] "

遊びなので全く気にしなくていいです。笑
 
さてさて、
色々漁ってきましたが、謎が多かったですね。
とりあえず「NSLog」と「os_log」との大きな違いはインスタンスを生成して使用する等、特別なオプションの有無でしょうか。
その他は出力内容も然程変わらないので使用する際は好みですかね。
「os_log」が出力制御できる分、カスタマイズ無しでも使いやすそうです。
 

<まとめ>

【NSLog】

  • 出力対象はNSString型、String型、文字列埋め込み、または、フォーマット指定子の対象となる型で指定
  • タイムスタンプの出力
  • フォーマット指定子による出力対象の書式変換

【os_log】

  • 出力対象はStaticString型(or 直接文字列を指定)、または、フォーマット指定子の対象となる型で指定
  • タイムスタンプの出力
  • フォーマット指定子による出力対象の書式変換
  • OSLogによる出力制御、出力オプションの指定

 

<おわりに>

今回は「NSLog」と「os_log」について漁りました!
癖はありますが、工夫が凝らされているログ達でしたね。
必要に迫られ脱線が多くなりこのボリュームになってしまいましたすみません/(^o^)\
 
次回はようやくさとこの3分カスタマイズです!
 
以上!

Shere
  • はてなブログ
  • Twitter
  • Facebook
Swift 3系、Xcode 8におけるログ出力関連機能を漁ってみた~その3~

Writer

  • Name

    さとこ

  • Position

    どこにもあと2歩くらい足りないシャイなエンジニア

  • Profile

    C/C++/C#/Objective-C/Swift/Java/Install Shiled Script言語/Oracle DB等。 触りましましたよ、ちょんちょんって。 PHP、始めました。